{"componentChunkName":"component---src-templates-course-js","path":"/course/streamcast","result":{"data":{"contentfulCourse":{"title":"StreamCast","image":"https://res.cloudinary.com/liquidcoder/image/upload/v1610888110/StreamCast/fqgloocb6kltur9h1vnn.jpg","slug":"streamcast","plan":"FREE","freeLessons":4,"updatedDate":"2 years ago","excerpt":"We are build a podcast player app from start to finish that will allow users to no only listen to podcast episodes, but also to download them locally and listen when you are offline and also sync them with iCloud.","createdDate":"Sunday, 17 January 2021","tags":[{"title":"Course"},{"title":"Swift UI"},{"title":"UI Design"},{"title":"iOS App Development"},{"title":"Mobile App Development"},{"title":"Real World iOS App"}],"resources":[{"name":"Starter project without multiple environments","url":"https://u.pcloud.link/publink/show?code=XZF6cYXZAFiR53aG9K4DrVNrhgKsc4kds3Jk"},{"name":"App icons","url":"https://u.pcloud.link/publink/show?code=XZSucYXZGHIsQANmhk5Ih8LIScYijkuloLm7"},{"name":"Combine free book","url":"https://heckj.github.io/swiftui-notes/#aboutthisbook"}],"lessons":[{"createdDate":"2 years ago","createdAt":"2021-01-17T19:00:48.508Z","isNewLayout":true,"previews":null,"slug":"lesson-1-project-setup","title":"Lesson #1: Project setup","updatedDate":"2021-01-17T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZCIcYXZko5q10ogVr56oTM1tVMihjofyzI7","type":"zip","id":"d43f5d2d-c7b4-5bac-99ca-b3a5e6f1c5f3","name":"Source code"}],"allChapters":["Requirements:Introduction","Getstarted:Get started","CreateWatchOSTarget: Watch OS Target","ProjectEnvironmentSetup: Project environment setup","Configurations:Configurations","Schemes:Schemes","ConfigurationSettings: Configuration Settings","PopulateXCConfig:Adding values in xcconfig files","Lesson1AppIconsFolder:App icons","ConfigPlist:Configs in info.plist and project settings"],"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Getstarted\",\n    className: \"code-block img-container\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610110353/StreamCast/qnxazhnaxgdrnurwxaoo.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610110353/StreamCast/qnxazhnaxgdrnurwxaoo.png\"\n  })))), mdx(\"div\", {\n    id: \"CheckNotificationSceneOSTarget\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610110486/StreamCast/e4pwcn5txjokxfhipz63.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610110486/StreamCast/e4pwcn5txjokxfhipz63.png\"\n  })))), mdx(\"div\", {\n    id: \"ComplicationSchemaWatchOS\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610110672/StreamCast/cspvkbgcjql5x3rz88dp.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610110672/StreamCast/cspvkbgcjql5x3rz88dp.png\"\n  })))), mdx(\"div\", {\n    id: \"supportIphoneWatchOSTarget\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610110762/StreamCast/wmjkskddtcgoom9dfazg.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610110762/StreamCast/wmjkskddtcgoom9dfazg.png\"\n  })))), mdx(\"div\", {\n    id: \"Configurations\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111034/StreamCast/go8xidcqbz5jq0isbaot.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111034/StreamCast/go8xidcqbz5jq0isbaot.png\"\n  })), \"  \"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610110902/StreamCast/dtojjjctw1mn2lv2ri4g.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610110902/StreamCast/dtojjjctw1mn2lv2ri4g.png\"\n  })))), mdx(\"div\", {\n    id: \"AddConfigurations\",\n    className: \"code-block\"\n  }), mdx(\"div\", {\n    id: \"FinishAddingConfigurations\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111113/StreamCast/e0g3a4fjuu26mzcxmh8v.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111113/StreamCast/e0g3a4fjuu26mzcxmh8v.png\"\n  })))), mdx(\"div\", {\n    id: \"OpenSchemesToolbar\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111392/StreamCast/ccq9suvtwce5abrh1boi.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111392/StreamCast/ccq9suvtwce5abrh1boi.png\"\n  })))), mdx(\"div\", {\n    id: \"OpenSchemeManager\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111423/StreamCast/veweud3jczmpajvxxke5.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111423/StreamCast/veweud3jczmpajvxxke5.png\"\n  })))), mdx(\"div\", {\n    id: \"WhatSchemeWeHave\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111503/StreamCast/tsyuplsjp3rv8qiaggie.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111503/StreamCast/tsyuplsjp3rv8qiaggie.png\"\n  })))), mdx(\"div\", {\n    id: \"AddingSchemes\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111867/StreamCast/u4bltjnkztycox5ucoyg.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111867/StreamCast/u4bltjnkztycox5ucoyg.png\"\n  })))), mdx(\"div\", {\n    id: \"EditingSchemes\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610112023/StreamCast/kpacr1u0xfvylqornq0l.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610112023/StreamCast/kpacr1u0xfvylqornq0l.png\"\n  })))), mdx(\"div\", {\n    id: \"FinishAddingSchemes\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610112145/StreamCast/ny7ogqss30ga2qqyflid.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610112145/StreamCast/ny7ogqss30ga2qqyflid.png\"\n  })))), mdx(\"div\", {\n    id: \"AddingConfigFiles\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610112320/StreamCast/edv0lqbgm9fgtqyje6km.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610112320/StreamCast/edv0lqbgm9fgtqyje6km.png\"\n  })))), mdx(\"div\", {\n    id: \"SelectTargetsAddingConfigs\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610115996/StreamCast/p6jg4wmhtbseegg344wo.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610115996/StreamCast/p6jg4wmhtbseegg344wo.png\"\n  })))), mdx(\"div\", {\n    id: \"LessonOneAllConfigSet\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111113/StreamCast/e0g3a4fjuu26mzcxmh8v.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111113/StreamCast/e0g3a4fjuu26mzcxmh8v.png\"\n  })), \"  \"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610116103/StreamCast/v9pjimq5cfiukgpto7lh.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610116103/StreamCast/v9pjimq5cfiukgpto7lh.png\"\n  })))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-xcconfig\"\n  }), \"// $$liquidcoderID-PopulateXCConfig\\n// Development.xcconfig\\n\\nAPP_BUNDLE_IDENTIFIER = com.liquidcoder.StreamCast.development\\n\\nAPP_NAME = StreamCast Development\\n\\nAPP_ICON_NAME = AppIcon.development\\n\\n// Staging.xcconfig\\n\\nAPP_BUNDLE_IDENTIFIER = com.liquidcoder.StreamCast.staging\\n\\nAPP_NAME = StreamCast Staging\\n\\nAPP_ICON_NAME = AppIcon.staging\\n\\n// Production.xcconfig\\n\\nAPP_BUNDLE_IDENTIFIER = com.liquidcoder.StreamCast\\n\\nAPP_NAME = StreamCast\\n\\nAPP_ICON_NAME = AppIcon\\n\\n\")), mdx(\"div\", {\n    id: \"Lesson1AppIconsFolder\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610119764/StreamCast/dqeu9o2k1d5tur4dioqb.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610119764/StreamCast/dqeu9o2k1d5tur4dioqb.png\"\n  })))), mdx(\"div\", {\n    id: \"Lesson1CreatePlaceholderAppIcons\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610119890/StreamCast/zb1pl6s5ku6d6p2kyr5l.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610119890/StreamCast/zb1pl6s5ku6d6p2kyr5l.png\"\n  })))), mdx(\"div\", {\n    id: \"Lesson1AddPlaceholderAppIcons\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120064/StreamCast/ymciimlcnjqrdytkviqn.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120064/StreamCast/ymciimlcnjqrdytkviqn.png\"\n  })))), mdx(\"div\", {\n    id: \"Lesson1AddMacAndWatchOSAppIcons\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120533/StreamCast/ljmvvp9pgmmzqvhd7m5o.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120533/StreamCast/ljmvvp9pgmmzqvhd7m5o.png\"\n  })))), mdx(\"div\", {\n    id: \"Lesson1AddSharedXCAsset\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Shared\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120655/StreamCast/l8fgrerywc5yneghxf6j.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120655/StreamCast/l8fgrerywc5yneghxf6j.png\"\n  }))), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"WatchOS\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120655/StreamCast/afnz7tij9hc4c5sifxxq.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120655/StreamCast/afnz7tij9hc4c5sifxxq.png\"\n  })))), mdx(\"div\", {\n    id: \"Lesson1FinishAddPlaceholderAppIcons\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120741/StreamCast/wkhgr5hvluynktmcmobg.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120741/StreamCast/wkhgr5hvluynktmcmobg.png\"\n  })))), mdx(\"div\", {\n    id: \"ConfigPlist\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"iOS\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120862/StreamCast/kxpqyog1abmbcxobxkbe.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120862/StreamCast/kxpqyog1abmbcxobxkbe.png\"\n  }))), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"macOS\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120862/StreamCast/nskwmdnmcalnz87wpwuw.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120862/StreamCast/nskwmdnmcalnz87wpwuw.png\"\n  })))), mdx(\"div\", {\n    id: \"WatchOS\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610880122/StreamCast/xshojkiu60c9u8jpoasz.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610880122/StreamCast/xshojkiu60c9u8jpoasz.png\"\n  })))), mdx(\"div\", {\n    id: \"WatchOSExtension\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610880123/StreamCast/tnafdghqxgwy6nwosduv.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610880123/StreamCast/tnafdghqxgwy6nwosduv.png\"\n  })))));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson1Introduction-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Welcome guys to the first lesson of the podcast app course. You are going to hopefully learn what you need to start your  iOS development career. In this first lesson, we are mainly going to focus on setting up the project environment. \")), mdx(\"div\", {\n    id: \"Requirements-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Requirements\"), mdx(\"p\", null, \"To follow along, you need:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Xcode 12 or greater\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"MacOS Big Sur or greater\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Swift 5.0 or greater\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"iOS 14 or greater\"))), mdx(\"div\", {\n    id: \"Getstarted-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Get started\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Open Xcode\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Create a new Xcode project\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Select App and click next\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Name your app  \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"StreamCast\")), \" and make sure the user interface is Swift UI\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Make sure to select '\", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Use Coredata\")), \"' and '\", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Host in CloudKit\")), \"'\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Then click \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Next\")), \".\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"You can choose to create git right now or you can create it later\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"And last click on the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"create\")), \" button\"))), mdx(\"div\", {\n    id: \"CreateWatchOSTarget-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Watch os target\"), mdx(\"p\", null, \"The project we've just created only has iOS/iPadOs and macOS targets, but does not have a watchOS one, so before going any further, let's add the watchOS target as well.\"), mdx(\"p\", null, \"Open the project if it is not already and click  \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"File \\u2192 New \\u2192 Target.\")), \" Select the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"watchOS tab, then watch App for iOS App.\"))), mdx(\"div\", {\n    id: \"CheckNotificationSceneOSTarget-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Then on the next screen, name the target watchOS and leave everything as is, but make sure  \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Include Notification Scene\")), \" is checked. \")), mdx(\"div\", {\n    id: \"ComplicationSchemaWatchOS-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"When prompted by Xcode to activate the complication scheme, click \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"cancel.\")))), mdx(\"div\", {\n    id: \"supportIphoneWatchOSTarget-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now that you have the watch os target created, let's take care of some minor details before we continue.\"), mdx(\"p\", null, \"Select the project (\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \".xcodeproj file\"), \"), then \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"watchOS\")), \" \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Extension,\")), \" make sure the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"support Running Without iOS App installation\")), \" is checked.\"), mdx(\"p\", null, \"And with that done, let's now setup the environment configurations.\")), mdx(\"div\", {\n    id: \"ProjectEnvironmentSetup-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Project environments setup\"), mdx(\"p\", null, \"It\\u2019s always a best practice to have separate environments for your iOS apps. let me explain.\"), mdx(\"p\", null, \"The purpose of us creating multple enviroments is to prevent messing with the production one. maybe you have 2 different api keys or api urls for testing and production, it would tedius and error-prone to change them every time you want to test one or the other, so it is important to take some time when you start a new project to setup your enviroments correctely. This will also allow you to install multiple variants of the same app on a single device. When you are done, you will able to run any of the environments on demand.\"), mdx(\"p\", null, \"We will have 3 environments, and each one of them will have a debug and a release configuration. So we will have:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Development:\"), \"  this is what we will mostly work with throughout this course.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Staging\"), \" : This is the one that you would typical give to your investors as a preview.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Production\"), \": This is the official one, the one to deploy to the app store\")), mdx(\"p\", null, \"Setting up different environments in \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Xcode\"), \" is not as straight forward as it should be, but it will make sense as we go along. \"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"If you don't want to setup the project with multiple environments, you can download the starter project without multiple environment that I have included to the project content in the floating menu.\"))), mdx(\"div\", {\n    id: \"Configurations-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Configurations\"), mdx(\"p\", null, \"Open the project settings by clicking on the project file, then select the project, and under the info tab, find the Configurations section. Right now, we only have 2 configurations. So Let's now add some more.\")), mdx(\"div\", {\n    id: \"AddConfigurations-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now click the plus (+) button under configurations, and duplicate each configuration two times, and rename the duplicates \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"[CONFIGURATION]\"), \".\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"[ENVIRONMENT]\"), \" and \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"[CONFIGURATION]\"), \".\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"[ENVIRONMENT]\"), \" where \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"[CONFIGURATION]\"), \" will either be Debug or Release and \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"[ENVIRONMENT]\"), \" will either be staging or development.  The original ones will stay that way because they will be used for production.\")), mdx(\"div\", {\n    id: \"FinishAddingConfigurations-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"When you are done, you should have something like this.\")), mdx(\"div\", {\n    id: \"Schemes-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Schemes\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"An Xcode scheme defines a collection of targets to build, a configuration to use when building, and a collection of tests to execute.\\nYou can have as many schemes as you want, but only one can be active at a time. You can specify whether a scheme should be stored in a project\\u2014in which case it\\u2019s available in every workspace that includes that project, or in the workspace\\u2014in which case it\\u2019s available only in that workspace. When you select an active scheme, you also select a run destination (that is, the architecture of the hardware for which the products are built).\")), mdx(\"p\", null, \"Every Xcode project comes with one scheme for both the debug and release configurations. Having added 2 more configurations (staging and development), we will also need to add 2 schemes for those as well.\")), mdx(\"div\", {\n    id: \"OpenSchemesToolbar-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"To add a new scheme, you need to open the scheme manager located in the Xcode \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"toolbar,\")), \" select whichever target is currently displayed, and click \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"manage schemes...\")), \" \")), mdx(\"div\", {\n    id: \"OpenSchemeManager-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Or go to \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Product \\u2192 Scheme \\u2192 Manage Schemes...\")))), mdx(\"div\", {\n    id: \"WhatSchemeWeHave-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Right now, here is what we have. We will need to duplicate each one of those twice, except the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"watchOS (Notification)\")), \". \")), mdx(\"div\", {\n    id: \"AddingSchemes-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The process is almost the same like what we've done with the configuration, but you will need to verify that the configurations are correctly set for each action. So click the plus \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"(+)\"), \" button, and choose the target you want to duplicate and rename it to \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"YouAppName.staging\"), \" or \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"YourAppName.development\"), \". Once that's done, select the scheme you've just added then click the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Edit...\")), \" button on the bottom-left corner\"), mdx(\"p\", null, \"Take a look at this image as an example :\")), mdx(\"div\", {\n    id: \"EditingSchemes-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"When you click on the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Edit...\")), \" button, another dialogue will be opened, and here we need to make some more changes.   Depending on whether it's staging or development, we need to set the correct \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"build configurations\"), \". We need to make sure the build configuration is set to the correct \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"[CONFIGURATION]\"), \".\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"[ENVIRONMENT]\", \".\"), \" So if, for example , the scheme you are editing is staging , you need to replace the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Debug\"), \" or \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Release\"), \" with \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Debug.staging\"), \" or \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Release.staging.\"), \" when you are done, click manage schemes again, and duplicate the next one and so on until you have 2 copies for each scheme.\")), mdx(\"div\", {\n    id: \"FinishAddingSchemes-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"When you are done, you should have something this:\")), mdx(\"div\", {\n    id: \"ConfigurationSettings-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Configuration settings\"), mdx(\"p\", null, \"So, we have all  configurations, environments and schemes stuff done, what's next?  The purpose of us creating multiple environments for each configuration is to allow us not only to build and run the app with different behaviours, but also to be able to install the same app with 3 different bundle id on a single device.\"), mdx(\"p\", null, \"So we need an easier way of setting and changing configurations values based on environment. We will use \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \".xcconfig\")), \" files for that job. \")), mdx(\"div\", {\n    id: \"AddingConfigFiles-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"So, in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Shared\")), \" folder, create a folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Configs,\")), \" and inside it create an \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"xcconfig\")), \" file. The process is the same as before, but this time you need to choose a \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Configuration Settings File.\")))), mdx(\"div\", {\n    id: \"SelectTargetsAddingConfigs-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Name the first file \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Development\")), \" , and make sure all targets are checked.\"), mdx(\"p\", null, \"Repeat the process for \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Staging\")), \" and \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Production.\")))), mdx(\"div\", {\n    id: \"FinishSettingConfigFilesConfigurations-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now that we have those config files, the next step, before adding anything inside them, is to add them to their respective configurations in the project settings. \"), mdx(\"p\", null, \"Open the project settings by clicking on the project file, then select the project, then under the info tab, find the Configurations section.  \")), mdx(\"div\", {\n    id: \"LessonOneAllConfigSet-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"As you can see, there's no configuration file set in the first picture... So let's do that now.  The \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Production.xcconfig\")), \" file will be set to the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Production\")), \" environment (the ones without an environment suffix), and the remaining 2 will be set respectively to the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"staging\"), \" and \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"development\")), \" environments\"), mdx(\"p\", null, \"When you are done, you have should something like the second picture\"), mdx(\"p\", null, \"As far as the hard stuff are concerned, we are done. Everything that remain  are relatively easy. \")), mdx(\"div\", {\n    id: \"PopulateXCConfig-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Adding values in xcconfig files\"), mdx(\"p\", null, \"The first values we will add are the names and bundle identifiers. As I said, different bundle identifiers will allow us to install  3 variants of the same app on the same device. \"), mdx(\"p\", null, \"Add the following values in each of the files:\")), mdx(\"div\", {\n    id: \"Lesson1AppIconsFolder-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"App icons\"), mdx(\"p\", null, \"Before we continue, you will need to download app icons. When you unzip the folder, you will find them like this.\"), mdx(\"p\", null, \"You will find the AppIcon link in the resources dropdown above.  \")), mdx(\"div\", {\n    id: \"Lesson1CreatePlaceholderAppIcons-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Just like the names in the config files, the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"AppIcons\")), \" in the asset catalogues will contain the same environment names after AppIcon, except the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Production\")), \" one.\"), mdx(\"p\", null, \"If you open the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Assets.xcassets\")), \" folder, you will see something like this:\")), mdx(\"div\", {\n    id: \"Lesson1AddPlaceholderAppIcons-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Leave that AppIcon as it is, it will be used for production... Let's now add staging and development ones. Inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Asset\")), \" folder, tap the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"\\\"+\\\"\"), \" button located at the bottom of the page, and select \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"iOS\")), \" then \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"iOS App Icon.\")), \" Rename the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"AppIcon-1\")), \" to \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"AppIcon.development.\")), \" \")), mdx(\"div\", {\n    id: \"Lesson1AddMacAndWatchOSAppIcons-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now while the new \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"AppIcon.development\")), \" is selected, open the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"project inspectors\"), \", and make sure \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Mac\")), \" and \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Apple Watch\")), \" are checked in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"attributes inspector\")), \" settings. When you are done,  do the same thing for staging.\")), mdx(\"div\", {\n    id: \"Lesson1AddSharedXCAsset-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now the next step is to copy the icons you've downloaded over to the project. However, before we do that, you will need to include the shared \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Assets.xcassets\")), \" folder to WatchOS target and remove the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Assets.xcassets\")), \" in the WatchOS for its target. While the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Assets.xcassets\")), \" folder is selected, open the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"file inspector\"), \" , located on the right of Xcode inside the project inspectors, and check the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"watchOS\"), \" target for the shared one and uncheck the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"watchOS\"), \" target in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"watchOS\"), \" folder, like this:\")), mdx(\"div\", {\n    id: \"Lesson1FinishAddPlaceholderAppIcons-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Having done that, you will now open the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Assets.xcassets\")), \" folder in finder and start copying the content of the Icons folders to your project . You will need to drag and drop the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"AppIcons\")), \" in the actual project, and when prompted to replace the older ones, click replace.   \")), mdx(\"div\", {\n    id: \"ConfigPlist-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Configs in info.plist and project settings\"), mdx(\"h3\", null, \"Info.plist\"), mdx(\"p\", null, \"We need to update the existing configuration values in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"info.plist.\")), \" The entries to update will be bundle identifier and the bundle name.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"iOS and macOS\"))), mdx(\"p\", null, \"Let's start with \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"info.plist\")), \" files first. We will first modify the iOS and macOS info.plist files. So, open the info.plist in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"iOS\")), \" target, and replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$(PRODUCT_BUNDLE_IDENTIFIER)\"), \"  with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$(APP_BUNDLE_IDENTIFIER)\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$(PRODUCT_NAME)\"), \" with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$(APP_NAME)\"), \" . \"), mdx(\"p\", null, \"The 2 info.plist files should look like this:\")), mdx(\"div\", {\n    id: \"WatchOS-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"watchOS and watchOS Extension\"))), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"watchOS\")), \" info.plist, we will change 3 configurations. You will need to replace  the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Bundle\"), \" \", mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"identifier\")), \" value with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$(APP_BUNDLE_IDENTIFIER).watchkitapp\"), \" and \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Bundle display name\")), \" with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$(APP_NAME)\"), \" . Now find the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"WKCompanionAppBundleIdentifier ,\")), \" and change its value to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$(APP_BUNDLE_IDENTIFIER)\"), \" .\")), mdx(\"div\", {\n    id: \"WatchOSExtension-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"watchOS Extension,\")), \" we will change 2 configurations. Replace the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"bundle identifier\")), \" value with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$(APP_BUNDLE_IDENTIFIER).watchkitapp.watchkitextension\"), \" . Last, find the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"WKAppBundleIdentifier,\")), \" located under \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"NSExtension \\u2192 NSExtensionAttributes,\")), \" replace its value with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$(APP_BUNDLE_IDENTIFIER).watchkitapp\"))), mdx(\"div\", {\n    id: \"ProjectSettings-Section\",\n    className: \"post-section\"\n  }, mdx(\"h3\", null, \"Project settings\"), mdx(\"p\", null, \"Open the project settings by clicking on the project file.\"), mdx(\"p\", null, \"Let's change the iOS target first. While the target is selected, start typing in the search field this \\\"bundle identifier\\\". Now replace the value of \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Product Bundle Identifier\"), \" with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$(APP_BUNDLE_IDENTIFIER)\"), \" , and when you tap return or enter, you will see every configuration has its corresponding bundle identifier. \"), mdx(\"p\", null, \"Again, start typing \\\"Product name\\\" in the search, and replace the value of the product name with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$(APP_NAME)\"), \". This works the same as the bundle identifier.\"), mdx(\"p\", null, \"And last for the iOS target, start typing \\\"asset catalogue\\\", and you should get something like few values, now under \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Asset Catalogue Compiler,\"), \" change the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Asset Catalogue App Icon Set Name \", mdx(\"inlineCode\", {\n    parentName: \"strong\"\n  }, \"$(APP_ICON_NAME)\"))), mdx(\"p\", null, \"Now you will do the exact same thing for the macOS target as well. However, for the watchOS and watchOS Extension targets, the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"bundle identifier\")), \" will be different, make sure it correspond to the values that are in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"info.plist\")), \" files respectively. For the App icon, only change the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"watchOS\")), \"  target.  \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Bundle display name\"))), mdx(\"p\", null, \"This is the end of lesson #1, if you want to test run each environment, just select a scheme in the XCode toolbar and choose a device to run on. Happy coding guys!\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#requirements","title":"Requirements"},{"url":"#get-started","title":"Get started"},{"url":"#watch-os-target","title":"Watch os target"},{"url":"#project-environments-setup","title":"Project environments setup"},{"url":"#configurations","title":"Configurations"},{"url":"#schemes","title":"Schemes"},{"url":"#configuration-settings","title":"Configuration settings"},{"url":"#adding-values-in-xcconfig-files","title":"Adding values in xcconfig files"},{"url":"#app-icons","title":"App icons"},{"url":"#configs-in-infoplist-and-project-settings","title":"Configs in info.plist and project settings","items":[{"url":"#infoplist","title":"Info.plist"},{"url":"#project-settings","title":"Project settings"}]}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Welcome guys to the first lesson of the podcast app course. You are going to hopefully learn what you need to start your  iOS development career. In this first lesson, we are mainly going to focus on setting up the project environment. \"), mdx(\"h2\", null, \"Requirements\"), mdx(\"p\", null, \"To follow along, you need:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Xcode 12 or greater\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"MacOS Big Sur or greater\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Swift 5.0 or greater\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"iOS 14 or greater\")), mdx(\"h2\", null, \"Get started\"), mdx(\"p\", null, \"  Open Xcode,\\nCreate a new Xcode project,\\nSelect App and click next\\nName your app \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"StreamCast\")), \" and make sure the user interface is Swift UI\\nMake sure to select '\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Use Coredata\")), \"' and '\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Host in CloudKit\")), \"'\"), mdx(\"p\", null, \" \", mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610110353/StreamCast/qnxazhnaxgdrnurwxaoo.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610110353/StreamCast/qnxazhnaxgdrnurwxaoo.png\"\n  }))), mdx(\"p\", null, \" Then click \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Next\")), \".\\nYou can choose to create git right now or you can create it later\\nAnd last click on the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"create\")), \" button\"), mdx(\"h2\", null, \"Watch os target\"), mdx(\"p\", null, \"The project we've just created only has iOS/iPadOs and macOS targets, but does not have a watchOS one, so before going any further, let's add the watchOS target as well.\"), mdx(\"p\", null, \"Open the project if it is not already and click  \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"File \\u2192 New \\u2192 Target.\")), \" Select the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"watchOS tab, then watch App for iOS App.\")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610110412/StreamCast/t2ndlgdxgnsedgimukrt.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610110412/StreamCast/t2ndlgdxgnsedgimukrt.png\"\n  }))), mdx(\"p\", null, \"Then on the next screen, name the target watchOS and leave everything as is, but make sure  \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Include Notification Scene\")), \" is checked. \"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610110486/StreamCast/e4pwcn5txjokxfhipz63.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610110486/StreamCast/e4pwcn5txjokxfhipz63.png\"\n  }))), mdx(\"p\", null, \"When prompted by Xcode to activate the complication scheme, click \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"cancel.\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610110672/StreamCast/cspvkbgcjql5x3rz88dp.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610110672/StreamCast/cspvkbgcjql5x3rz88dp.png\"\n  }))), mdx(\"p\", null, \"Now that you have the watch os target created, let's take care of some minor details before we continue.\"), mdx(\"p\", null, \"Select the project (\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \".xcodeproj file\"), \"), then \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"watchOS\")), \" \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Extension,\")), \" make sure the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"support Running Without iOS App installation\")), \" is checked.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610110762/StreamCast/wmjkskddtcgoom9dfazg.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610110762/StreamCast/wmjkskddtcgoom9dfazg.png\"\n  }))), mdx(\"p\", null, \"And with that done, let's now setup the environment configurations.\"), mdx(\"h2\", null, \"Project environment setup\"), mdx(\"p\", null, \"It\\u2019s always a best practice to have separate environments for your iOS apps. let me explain. \"), mdx(\"p\", null, \"The purpose of us creating multple enviroments is to prevent messing with the production one. maybe you have 2 different api keys or api urls for testing and production, it would tedius and error-prone to change them every time you want to test one or the other, so it is important to take some time when you start a new project to setup your enviroments correctely. This will also allow you to install multiple variants of the same app on a single device. When you are done, you will able to run any of the environments on demand.\"), mdx(\"p\", null, \"We will have 3 environments, and each one of them will have a debug and a release configuration. So we will have:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Development:\"), \"  this is what we will mostly work with throughout this course.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Staging\"), \" : This is the one that you would typical give to your investors as a preview.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Production\"), \": This is the official one, the one to deploy to the app store\")), mdx(\"p\", null, \"Setting up different environments in \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Xcode\"), \" is not as straight forward as it should be, but it will make sense as we go along. \"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"If you don't want to setup the project with multiple environments, you can download the starter project without multiple environment that I have included to the project content in the floating menu.\")), mdx(\"h2\", null, \"Configurations\"), mdx(\"p\", null, \"Open the project settings by clicking on the project file, then select the project, and under the info tab, find the Configurations section. Right now, we only have 2 configurations. So Let's now add some more.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610110902/StreamCast/dtojjjctw1mn2lv2ri4g.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610110902/StreamCast/dtojjjctw1mn2lv2ri4g.png\"\n  }))), mdx(\"p\", null, \"Now click the plus (+) button under configurations, and duplicate each configuration two times, and rename the duplicates \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"[CONFIGURATION]\"), \".\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"[ENVIRONMENT]\"), \" and \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"[CONFIGURATION]\"), \".\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"[ENVIRONMENT]\"), \" where \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"[CONFIGURATION]\"), \" will either be Debug or Release and \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"[ENVIRONMENT]\"), \" will either be staging or development.  The original ones will stay that way because they will be used for production.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111034/StreamCast/go8xidcqbz5jq0isbaot.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111034/StreamCast/go8xidcqbz5jq0isbaot.png\"\n  }))), mdx(\"p\", null, \"When you are done, you should have something like this.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111113/StreamCast/e0g3a4fjuu26mzcxmh8v.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111113/StreamCast/e0g3a4fjuu26mzcxmh8v.png\"\n  }))), mdx(\"h2\", null, \"Schemes\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"An Xcode scheme defines a collection of targets to build, a configuration to use when building, and a collection of tests to execute.\\nYou can have as many schemes as you want, but only one can be active at a time. You can specify whether a scheme should be stored in a project\\u2014in which case it\\u2019s available in every workspace that includes that project, or in the workspace\\u2014in which case it\\u2019s available only in that workspace. When you select an active scheme, you also select a run destination (that is, the architecture of the hardware for which the products are built).\")), mdx(\"p\", null, \"Every Xcode project comes with one scheme for both the debug and release configurations. Having added 2 more configurations (staging and development), we will also need to add 2 schemes for those as well.\"), mdx(\"p\", null, \"To add a new scheme, you need to open the scheme manager located in the Xcode \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"toolbar,\")), \" select whichever target is currently displayed, and click \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"manage schemes...\")), \" \"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111392/StreamCast/ccq9suvtwce5abrh1boi.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111392/StreamCast/ccq9suvtwce5abrh1boi.png\"\n  }))), mdx(\"p\", null, \"Or go to \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Product \\u2192 Scheme \\u2192 Manage Schemes...\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111423/StreamCast/veweud3jczmpajvxxke5.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111423/StreamCast/veweud3jczmpajvxxke5.png\"\n  }))), mdx(\"p\", null, \"Right now, here is what we have. We will need to duplicate each one of those twice, except the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"watchOS (Notification)\")), \". \"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111503/StreamCast/tsyuplsjp3rv8qiaggie.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111503/StreamCast/tsyuplsjp3rv8qiaggie.png\"\n  }))), mdx(\"p\", null, \"The process is almost the same like what we've done with the configuration, but you will need to verify that the configurations are correctly set for each action. So click the plus \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"(+)\"), \" button, and choose the target you want to duplicate and rename it to \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"YouAppName.staging\"), \" or \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"YourAppName.development\"), \". Once that's done, select the scheme you've just added then click the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Edit...\")), \" button on the bottom-left corner\"), mdx(\"p\", null, \"Take a look at this image as an example :\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111867/StreamCast/u4bltjnkztycox5ucoyg.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111867/StreamCast/u4bltjnkztycox5ucoyg.png\"\n  }))), mdx(\"p\", null, \"When you click on the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Edit...\")), \" button, another dialogue will be opened, and here we need to make some more changes.   Depending on whether it's staging or development, we need to set the correct \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"build configurations\"), \". We need to make sure the build configuration is set to the correct \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"[CONFIGURATION]\"), \".\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"[ENVIRONMENT]\", \".\"), \" So if, for example , the scheme you are editing is staging , you need to replace the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Debug\"), \" or \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Release\"), \" with \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Debug.staging\"), \" or \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Release.staging.\"), \" when you are done, click manage schemes again, and duplicate the next one and so on until you have 2 copies for each scheme.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610112023/StreamCast/kpacr1u0xfvylqornq0l.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610112023/StreamCast/kpacr1u0xfvylqornq0l.png\"\n  }))), mdx(\"p\", null, \"When you are done, you should have something this:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610112145/StreamCast/ny7ogqss30ga2qqyflid.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610112145/StreamCast/ny7ogqss30ga2qqyflid.png\"\n  }))), mdx(\"h2\", null, \"Configuration settings\"), mdx(\"p\", null, \"So, we have all  configurations, environments and schemes stuff done, what's next?  The purpose of us creating multiple environments for each configuration is to allow us not only to build and run the app with different behaviours, but also to be able to install the same app with 3 different bundle id on a single device.\"), mdx(\"p\", null, \"So we need an easier way of setting and changing configurations values based on environment. We will use \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \".xcconfig\")), \" files for that job. \"), mdx(\"p\", null, \"So, in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Shared\")), \" folder, create a folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Configs,\")), \" and inside it \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"**\"), \"create an \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"xcconfig\")), \" file. The process is the same as before, but this time you need to choose a \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Configuration Settings File.\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610112320/StreamCast/edv0lqbgm9fgtqyje6km.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610112320/StreamCast/edv0lqbgm9fgtqyje6km.png\"\n  }))), mdx(\"p\", null, \"Name the first file \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Development\")), \" , and make sure all targets are checked.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610115996/StreamCast/p6jg4wmhtbseegg344wo.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610115996/StreamCast/p6jg4wmhtbseegg344wo.png\"\n  }))), mdx(\"p\", null, \"Repeat the process for \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Staging\")), \" and \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Production.\"))), mdx(\"p\", null, \"Now that we have those config files, the next step, before adding anything inside them, is to add them to their respective configurations in the project settings. \"), mdx(\"p\", null, \"Open the project settings by clicking on the project file, then select the project, then under the info tab, find the Configurations section.  \"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111113/StreamCast/e0g3a4fjuu26mzcxmh8v.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610111113/StreamCast/e0g3a4fjuu26mzcxmh8v.png\"\n  }))), mdx(\"p\", null, \"As you can see above, there's no configuration file set... So let's do that now.  The \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Production.xcconfig\")), \" file will be set to the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Production\")), \" environment (the ones without an environment suffix), and the remaining 2 will be set respectively to the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"staging\"), \" and \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"development\")), \" environments\"), mdx(\"p\", null, \"When you are done, you have should something like this:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610116103/StreamCast/v9pjimq5cfiukgpto7lh.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610116103/StreamCast/v9pjimq5cfiukgpto7lh.png\"\n  }))), mdx(\"p\", null, \"As far as the hard stuff are concerned, we are done. Everything that remain  are relatively easy. \"), mdx(\"h2\", null, \"Adding values in xcconfig files\"), mdx(\"p\", null, \"The first values we will add are the names and bundle identifiers. As I said, different bundle identifiers will allow us to install  3 variants of the same app on the same device. \"), mdx(\"p\", null, \"Add the following values in each of the files:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Development.xcconfig\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-bash\"\n  }), \"APP_BUNDLE_IDENTIFIER = com.liquidcoder.StreamCast.development\\n\\nAPP_NAME = StreamCast Development\\n\\nAPP_ICON_NAME = AppIcon.development\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Staging.xcconfig\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-bash\"\n  }), \"APP_BUNDLE_IDENTIFIER = com.liquidcoder.StreamCast.staging\\n\\nAPP_NAME = StreamCast Staging\\n\\nAPP_ICON_NAME = AppIcon.staging\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Production.xcconfig\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-bash\"\n  }), \"APP_BUNDLE_IDENTIFIER = com.liquidcoder.StreamCast\\n\\nAPP_NAME = StreamCast\\n\\nAPP_ICON_NAME = AppIcon\\n\")), mdx(\"h2\", null, \"App icons\"), mdx(\"p\", null, \"Before we continue, you will need to download app icons. When you unzip the folder, you will find them like this.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610119764/StreamCast/dqeu9o2k1d5tur4dioqb.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610119764/StreamCast/dqeu9o2k1d5tur4dioqb.png\"\n  }))), mdx(\"p\", null, \"Just like the names in the config files, the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"AppIcons\")), \" in the asset catalogues will contain the same environment names after AppIcon, except the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Production\")), \" one.\"), mdx(\"p\", null, \"If you open the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Assets.xcassets\")), \" folder, you will see something like this:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610119890/StreamCast/zb1pl6s5ku6d6p2kyr5l.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610119890/StreamCast/zb1pl6s5ku6d6p2kyr5l.png\"\n  }))), mdx(\"p\", null, \"Leave that AppIcon as it is, it will be used for production... Let's now add staging and development ones. Inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Asset\")), \" folder, tap the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"\\\"+\\\"\")), \" button located at the bottom of the page, and select \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"iOS\")), \" then \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"iOS App Icon.\")), \" Rename the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"AppIcon-1\")), \" to \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"AppIcon.development.\")), \" \"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120064/StreamCast/ymciimlcnjqrdytkviqn.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120064/StreamCast/ymciimlcnjqrdytkviqn.png\"\n  }))), mdx(\"p\", null, \"Now while the new \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"AppIcon.development\")), \" is selected, open the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"project inspectors\"), \", and make sure \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Mac\")), \" and \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Apple Watch\")), \" are checked in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"attributes inspector\")), \" settings. When you are done,  do the same thing for staging.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120533/StreamCast/ljmvvp9pgmmzqvhd7m5o.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120533/StreamCast/ljmvvp9pgmmzqvhd7m5o.png\"\n  }))), mdx(\"p\", null, \"Now the next step is to copy the icons you've downloaded over to the project. However, before we do that, you will need to include the shared \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Assets.xcassets\")), \" folder to WatchOS target and remove the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Assets.xcassets\")), \" in the WatchOS for its target. While the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Assets.xcassets\")), \" folder is selected, open the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"file inspector\"), \" , located on the right of Xcode inside the project inspectors, and check the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"watchOS\"), \" target for the shared one and uncheck the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"watchOS\"), \" target in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"watchOS\"), \" folder, like this:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Shared\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120655/StreamCast/l8fgrerywc5yneghxf6j.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120655/StreamCast/l8fgrerywc5yneghxf6j.png\"\n  }))), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"WatchOS\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120655/StreamCast/afnz7tij9hc4c5sifxxq.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120655/StreamCast/afnz7tij9hc4c5sifxxq.png\"\n  }))), mdx(\"p\", null, \"Having done that, you will now open the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Assets.xcassets\")), \" folder in finder and start copying the content of the Icons folders to your project . You will need to drag and drop the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"AppIcons\")), \" in the actual project, and when prompted to replace the older ones, click replace.   \"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120741/StreamCast/wkhgr5hvluynktmcmobg.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120741/StreamCast/wkhgr5hvluynktmcmobg.png\"\n  }))), mdx(\"h2\", null, \"Configs in info.plist and project settings\"), mdx(\"h3\", null, \"Info.plist\"), mdx(\"p\", null, \"We need to update the existing configuration values in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"info.plist.\")), \" The entries to update will be bundle identifier and the bundle name.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"iOS and macOS\"))), mdx(\"p\", null, \"Let's start with \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"info.plist\")), \" files first. We will first modify the iOS and macOS info.plist files. So, open the info.plist in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"iOS\")), \" target, and replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$(PRODUCT_BUNDLE_IDENTIFIER)\"), \"  with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$(APP_BUNDLE_IDENTIFIER)\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$(PRODUCT_NAME)\"), \" with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$(APP_NAME)\"), \" . \"), mdx(\"p\", null, \"The 2 info.plist files should look like this:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"iOS\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120862/StreamCast/kxpqyog1abmbcxobxkbe.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120862/StreamCast/kxpqyog1abmbcxobxkbe.png\"\n  }))), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"macOS\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120862/StreamCast/nskwmdnmcalnz87wpwuw.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610120862/StreamCast/nskwmdnmcalnz87wpwuw.png\"\n  }))), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"watchOS and watchOS Extension\"))), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"watchOS\")), \" info.plist, we will change 3 configurations. You will need to replace  the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Bundle\"), \" \", mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"identifier\")), \" value with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$(APP_BUNDLE_IDENTIFIER).watchkitapp\"), \" and \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Bundle display name\")), \" with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$(APP_NAME)\"), \" . Now find the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"WKCompanionAppBundleIdentifier ,\")), \" and change its value to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$(APP_BUNDLE_IDENTIFIER)\"), \" .\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610880122/StreamCast/xshojkiu60c9u8jpoasz.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610880122/StreamCast/xshojkiu60c9u8jpoasz.png\"\n  }))), mdx(\"p\", null, \"Next in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"watchOS Extension,\")), \" we will change 2 configurations. Replace the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"bundle identifier\")), \" value with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$(APP_BUNDLE_IDENTIFIER).watchkitapp.watchkitextension\"), \" . Last, find the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"WKAppBundleIdentifier,\")), \" located under \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"NSExtension \\u2192 NSExtensionAttributes,\")), \" replace its value with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$(APP_BUNDLE_IDENTIFIER).watchkitapp\")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610880123/StreamCast/tnafdghqxgwy6nwosduv.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610880123/StreamCast/tnafdghqxgwy6nwosduv.png\"\n  }))), mdx(\"h3\", null, \"Project settings\"), mdx(\"p\", null, \"Open the project settings by clicking on the project file.\"), mdx(\"p\", null, \"Let's change the iOS target first. While the target is selected, start typing in the search field this \\\"bundle identifier\\\". Now replace the value of \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Product Bundle Identifier\"), \" with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$(APP_BUNDLE_IDENTIFIER)\"), \" , and when you tap return or enter, you will see every configuration has its corresponding bundle identifier. \"), mdx(\"p\", null, \"Again, start typing \\\"Product name\\\" in the search, and replace the value of the product name with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$(APP_NAME)\"), \". This works the same as the bundle identifier.\"), mdx(\"p\", null, \"And last for the iOS target, start typing \\\"asset catalogue\\\", and you should get something like few values, now under \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Asset Catalogue Compiler,\"), \" change the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Asset Catalogue App Icon Set Name \", mdx(\"inlineCode\", {\n    parentName: \"strong\"\n  }, \"$(APP_ICON_NAME)\"))), mdx(\"p\", null, \"Now you will do the exact same thing for the macOS target as well. However, for the watchOS and watchOS Extension targets, the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"bundle identifier\")), \" will be different, make sure it correspond to the values that are in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"info.plist\")), \" files respectively. For the App icon, only change the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"watchOS\")), \"  target.  \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Bundle display name\"))), mdx(\"p\", null, \"This is the end of lesson #1, if you want to test run each environment, just select a scheme in the XCode toolbar and choose a device to run on. Happy coding guys!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#requirements","title":"Requirements"},{"url":"#get-started","title":"Get started"},{"url":"#watch-os-target","title":"Watch os target"},{"url":"#project-environment-setup","title":"Project environment setup"},{"url":"#configurations","title":"Configurations"},{"url":"#schemes","title":"Schemes"},{"url":"#configuration-settings","title":"Configuration settings"},{"url":"#adding-values-in-xcconfig-files","title":"Adding values in xcconfig files"},{"url":"#app-icons","title":"App icons"},{"url":"#configs-in-infoplist-and-project-settings","title":"Configs in info.plist and project settings"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-01-17T19:00:48.607Z","isNewLayout":true,"previews":{"previews":"{\n    \"DemoID0\": [\n        \"https://res.cloudinary.com/liquidcoder/image/upload/v1611829523/StreamCast/v5hom6ssy3jy8z3eb7zm.png\",\n        \"https://res.cloudinary.com/liquidcoder/image/upload/v1611682555/StreamCast/l5isqi1sdn3ugwn3w7qz.jpg\"\n    ],\n    \"DemoID2\": [\n        \"https://res.cloudinary.com/liquidcoder/image/upload/v1611828515/StreamCast/qzqj1t5j0y0y1hkzbdiz.png\",\n        \"https://res.cloudinary.com/liquidcoder/image/upload/v1611682555/StreamCast/l5isqi1sdn3ugwn3w7qz.jpg\"\n    ],\n    \"DemoID5\": [\n        \"https://res.cloudinary.com/liquidcoder/image/upload/v1611828191/StreamCast/i9hlatqfbs8mgwyo4lat.png\"\n    ]\n}"},"slug":"lesson-2-swiftlint-and-models","title":"Lesson #2: SwiftLint & Models","updatedDate":"2021-01-17T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZorcYXZYBlRfmmdPrmUrTpDqAS8MyOwoN9X","type":"zip","id":"e7b2870e-e784-5833-9459-34e6b6e8473c","name":"Source code"},{"url":"https://brew.sh","type":"link","id":"7af1137d-ae98-57ad-8bd8-5f9324344bc5","name":"Homebrew"},{"url":"https://realm.github.io/SwiftLint/rule-directory.html","type":"link","id":"0bc2b980-c4ee-5f50-a5eb-7d7ad72ef519","name":"Swiftlint rules"},{"url":"https://realm.github.io/SwiftLint/","type":"link","id":"97895382-6b44-5ed9-bb6e-3f1d46956f89","name":"Swiftlint"},{"url":"https://github.com/nmdias/FeedKit.git","type":"link","id":"5bd069b7-c9c4-5e96-80c9-2313a46f6016","name":"FeedKit"}],"allChapters":["Lesson2Introduction: Introduction","Lesson2SwiftLint: SwiftLint","Lesson2AddSwiftLint: Adding SwiftLint in the project","Lesson2SwiftLintRuleMod: Modifying swiftLint rules","Lesson2PodcastRestApiIntro: Podcast rest api","Lesson2FeedKitIntro: FeedKit","Lesson2Models1: Models","Lesson2ViewModelsIntro: ViewModels"],"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-json\"\n  }), \"// $$liquidcoderID-Lesson2BrewInstall\\n\\nbrew install swiftlint\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-json\"\n  }), \"// $$liquidcoderID-Lesson2AddSwiftLint\\nif which swiftlint >/dev/null; then\\n  swiftlint\\nelse\\n  echo \\\"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\\\"\\nfi\\n\\n\")), mdx(\"div\", {\n    id: \"Lesson2FinishAddSwiftLint\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610200593/StreamCast/idfs8efi2gke8seqkptl.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610200593/StreamCast/idfs8efi2gke8seqkptl.png\"\n  })))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-json\"\n  }), \"\\n// $$liquidcoderID-Lesson2AddSwiftLintRulesCode\\n\\ndisabled_rules:\\n- trailing_whitespace\\n\\nopt_in_rules:\\n- empty_count\\n- empty_string\\n\\nexcluded:\\n- Carthage\\n- Pods\\n- SwiftLint/Common/3rdPartyLib\\n\\nline_length:\\n    warning: 150\\n    error: 200\\n    ignores_function_declarations: true\\n    ignores_comments: true\\n    ignores_urls: true\\n\\nfunction_body_length:\\n    warning: 300\\n    error: 500\\n\\nfunction_parameter_count:\\n    warning: 5\\n    error: 7\\n\\ntype_body_length:\\n    warning: 300\\n    error: 500\\n\\nfile_length:\\n    warning: 1000\\n    error: 1500\\n    ignore_comment_only_lines: true\\n\\ncyclomatic_complexity:\\n    warning: 15\\n    error: 25\\n\\nreporter: \\\"xcode\\\"\\n\")), mdx(\"div\", {\n    id: \"Lesson2PodcastRestApiIntro2\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610886845/StreamCast/rgcadomz61hcxsd4splz.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610886845/StreamCast/rgcadomz61hcxsd4splz.png\"\n  })))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-bash\"\n  }), \"import Foundation\\n\\n// $$liquidcoderID-Lesson2PodcastModels\\n// $$liquidcoderFilename-Podcast\\n\\nstruct PodcastList: Codable {\\n    let resultCount: Int\\n    let results: [Podcast]\\n}\\n\\nstruct Podcast: Codable {\\n    var artistID: Int?\\n    var collectionID, trackID: Int\\n    var description: String?\\n    var artistName, collectionName, trackName: String?\\n    var feedURL: String?\\n    var artworkUrl30, artworkUrl60, artworkUrl100: String?\\n    var releaseDate: String?\\n    var collectionExplicitness, trackExplicitness: String?\\n    var trackCount: Int?\\n    var country, primaryGenreName: String?\\n    var contentAdvisoryRating: String?\\n    var artworkUrl600: String?\\n    var genres: [String]?\\n\\n    enum CodingKeys: String, CodingKey {\\n        case artistID = \\\"artistId\\\"\\n        case collectionID = \\\"collectionId\\\"\\n        case trackID = \\\"trackId\\\"\\n        case artistName, collectionName, trackName\\n        case feedURL = \\\"feedUrl\\\"\\n        case artworkUrl30, artworkUrl60, artworkUrl100\\n        case releaseDate, collectionExplicitness, trackExplicitness, trackCount, country, primaryGenreName, contentAdvisoryRating, artworkUrl600\\n        case genres\\n    }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-json\"\n  }), \"// $$liquidcoderID-Lesson2PodcastModels2\\n\\n{\\n    \\\"resultCount\\\": 50,\\n    \\\"results\\\": [{...},{...}, ...]\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\n// $$liquidcoderID-Lesson2AddGenreModel1\\n// $$liquidcoderFilename-Genre\\n\\nstruct GenreList: Codable {\\n    let genres: [Genre]\\n}\\n\\nstruct Genre: Codable {\\n    let id: Int\\n    let name: String\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-json\"\n  }), \"// $$liquidcoderID-Lesson2AddGenreModel2\\n\\n{\\n    \\\"genres\\\": [\\n        {\\n            \\\"id\\\": 144,\\n            \\\"name\\\": \\\"Finance\\\",\\n        },\\n            ...\\n        ]\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson2ViewModelsIntro\\n// $$liquidcoderFilename-GenreViewModel\\n\\nimport Foundation\\n\\nstruct GenreViewModel: Identifiable, Hashable {\\n    var id: Int\\n    var name: String\\n\\n    static let placeholder = createPlaceholder(id: 1)\\n\\n    static var listPlaceholders: [GenreViewModel] {\\n        (0..<15).map {\\n            createPlaceholder(id: $0)\\n        }\\n    }\\n\\n    private static func createPlaceholder(id: Int) -> GenreViewModel {\\n        GenreViewModel( Genre(id: id, name: \\\"Technology\\\") )\\n    }\\n\\n    init(_ genre: Genre) {\\n        self.id = genre.id\\n        self.name = genre.name\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson2EpisodeViewModelExplain1\\n// $$liquidcoderFilename-EpisodeViewModel\\n\\nimport Foundation\\n// highlight-start\\nimport FeedKit\\n// highlight-end\\n\\nstruct EpisodeViewModel: Identifiable {\\n\\n// highlight-start\\n    let id = UUID()\\n    var title, description: String\\n    var date: String\\n    var audioUrl: String\\n    var audio: Data?\\n    var audioLength: Double\\n    var audioSize: String\\n    var podcast: PodcastViewModel = .placeholder\\n\\n// highlight-end\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson2EpisodeViewModelExplain2\\n// $$liquidcoderFilename-EpisodeViewModel\\n\\nimport Foundation\\nimport FeedKit\\n\\nstruct EpisodeViewModel: Identifiable {\\n\\n    ...\\n\\n// highlight-start\\n    static var placeholder: EpisodeViewModel {\\n        createPlaceholder()\\n    }\\n// highlight-end\\n\\n// highlight-start\\n    static var listPlaceholder: [EpisodeViewModel] {\\n        (0..<10).map { _ in\\n            createPlaceholder()\\n        }\\n    }\\n// highlight-end\\n\\n// highlight-start\\n    private static func createPlaceholder() -> EpisodeViewModel {\\n        EpisodeViewModel(title: \\\"Lorem ipsum\\\",\\n                                description: \\\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\\\",\\n                                date: \\\"20 sept 2020\\\",\\n                                audio: \\\"\\\",\\n                                audioLength: 120,\\n                                audioSize: \\\"13mb\\\",\\n                                podcast: PodcastViewModel.placeholder)\\n    }\\n\\n// highlight-end\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson2EpisodeViewModelExplain3\\n// $$liquidcoderFilename-EpisodeViewModel\\n\\nimport Foundation\\nimport FeedKit\\n\\nstruct EpisodeViewModel: Identifiable {\\n\\n    ...\\n\\n    static var placeholder: EpisodeViewModel {\\n        createPlaceholder()\\n    }\\n\\n    static var listPlaceholder: [EpisodeViewModel] {\\n        (0..<10).map { _ in\\n            createPlaceholder()\\n        }\\n    }\\n\\n    private static func createPlaceholder() -> EpisodeViewModel {\\n        EpisodeViewModel(title: \\\"Lorem ipsum\\\",\\n                                description: \\\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\\\",\\n                                date: \\\"20 sept 2020\\\",\\n                                audio: \\\"\\\",\\n                                audioLength: 120,\\n                                audioSize: \\\"13mb\\\",\\n                                podcast: PodcastViewModel.placeholder)\\n    }\\n\\n// highlight-start\\n    internal init(title: String, description: String, date: String, audio: String, audioLength: Double, audioSize: String, podcast: PodcastViewModel) {\\n        self.title = title\\n        self.description = description\\n        self.date = date\\n        self.audioUrl = audio\\n        self.audioLength = audioLength\\n        self.audioSize = audioSize\\n        self.podcast = podcast\\n    }\\n// highlight-end\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson2EpisodeViewModelExplain4\\n// $$liquidcoderFilename-EpisodeViewModel\\n\\nimport Foundation\\nimport FeedKit\\n\\nstruct EpisodeViewModel: Identifiable {\\n\\n    ...\\n\\n    static var placeholder: EpisodeViewModel {\\n        createPlaceholder()\\n    }\\n\\n    static var listPlaceholder: [EpisodeViewModel] {\\n        (0..<10).map { _ in\\n            createPlaceholder()\\n        }\\n    }\\n\\n    private static func createPlaceholder() -> EpisodeViewModel {\\n        EpisodeViewModel(title: \\\"Lorem ipsum\\\",\\n                                description: \\\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\\\",\\n                                date: \\\"20 sept 2020\\\",\\n                                audio: \\\"\\\",\\n                                audioLength: 120,\\n                                audioSize: \\\"13mb\\\",\\n                                podcast: PodcastViewModel.placeholder)\\n    }\\n\\ninternal init(title: String, description: String, date: String, audio: String, audioLength: Double, audioSize: String, podcast: PodcastViewModel) { ... }\\n\\n// highlight-start\\n    init(feedItem: RSSFeedItem, podcast: PodcastViewModel) {\\n        self.title = feedItem.title ?? \\\"\\\"\\n        self.description = feedItem.description ?? \\\"\\\"\\n        self.date = feedItem.pubDate.map { $0.format() } ?? \\\"\\\"\\n        self.audioUrl = feedItem.enclosure?.attributes?.url ?? \\\"\\\"\\n        self.audioLength = feedItem.iTunes?.iTunesDuration ?? 0\\n\\n        self.audioSize = feedItem.enclosure?.attributes?.length.map {\\n            \\\"\\\\($0 / 1000000) mb\\\"\\n        } ?? \\\"0 mb\\\"\\n\\n        self.podcast = podcast\\n    }\\n// highlight-end\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson2PodcastViewModelExplain1\\n// $$liquidcoderFilename-PodcastViewModel\\n\\nimport Foundation\\nimport FeedKit\\n\\nstruct PodcastViewModel: Identifiable, Equatable {\\n    var id: Int\\n    var title: String\\n    var publisher: String = \\\"\\\"\\n    var imageUrl: String\\n    var thumbnailUrl: String = \\\"\\\"\\n    var image = Data()\\n    var thumbnail = Data()\\n    var author: String = \\\"\\\"\\n    var totalEpisodes: Int\\n    var explicitContent: String\\n    var description: String\\n    var date: String = \\\"\\\"\\n    var language: String?\\n    var country: String\\n    var genres: [String] = []\\n    var episodes: [EpisodeViewModel] = []\\n    var nextEpisodePubDate: Date\\n    var feedUrl: String\\n    var isBookmarked = false\\n    static var placeholder: PodcastViewModel {\\n        createPlaceholder(id: 0)\\n    }\\n    static var listPlaceholder: [PodcastViewModel] {\\n        (0..<10).map {\\n            createPlaceholder(id: $0)\\n        }\\n    }\\n    private static func createPlaceholder(id: Int) -> PodcastViewModel {\\n        PodcastViewModel(id: id,\\n                         title: \\\"Lorem ipsum\\\",\\n                         imageUrl: \\\"\\\",\\n                         totalEpisodes: 23,\\n                         explicitContent: \\\"No\\\",\\n                         description: \\\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\\\",\\n                         country: \\\"US\\\",\\n                         nextEpisodePubDate: Date() ,\\n                         feedUrl: \\\"\\\")\\n    }\\n    internal init(id: Int, title: String, publisher: String? = nil, imageUrl: String, thumbnailUrl: String? = nil, image: Data? = nil, thumbnail: Data? = nil, author: String? = nil, totalEpisodes: Int, explicitContent: String, description: String, date: String? = nil, language: String? = nil, country: String, genres: [String]? = nil, episodes: [EpisodeViewModel]? = nil, nextEpisodePubDate: Date, feedUrl: String, isBookmarked: Bool = false) {\\n        self.id = id\\n        self.title = title\\n        self.publisher = publisher ?? \\\"\\\"\\n        self.imageUrl = imageUrl\\n        self.thumbnailUrl = thumbnailUrl ?? \\\"\\\"\\n        self.image = image ?? Data()\\n        self.thumbnail = thumbnail ?? Data()\\n        self.author = author ?? \\\"\\\"\\n        self.totalEpisodes = totalEpisodes\\n        self.explicitContent = explicitContent\\n        self.description = description\\n        self.date = date ?? \\\"\\\"\\n        self.language = language\\n        self.country = country\\n        self.genres = genres ?? []\\n        self.episodes = episodes ?? []\\n        self.nextEpisodePubDate = nextEpisodePubDate\\n        self.feedUrl = feedUrl\\n        self.isBookmarked = isBookmarked\\n    }\\n    init(podcast: Podcast) {\\n        self.id = podcast.collectionID\\n        self.title = podcast.collectionName ?? \\\"\\\"\\n        self.publisher = \\\"\\\"\\n        self.imageUrl = podcast.artworkUrl600 ?? \\\"\\\"\\n        self.thumbnailUrl = podcast.artworkUrl100 ?? \\\"\\\"\\n        self.author = podcast.artistName ?? \\\"\\\"\\n        self.totalEpisodes = podcast.trackCount ?? 0\\n        self.explicitContent = podcast.collectionExplicitness ?? \\\"\\\"\\n        self.description = podcast.description ?? \\\"\\\"\\n        self.date = podcast.releaseDate?.formatDate() ?? \\\"\\\"\\n        self.language = nil\\n        self.country = podcast.country ?? \\\"\\\"\\n        self.genres = podcast.genres ?? []\\n        self.episodes = []\\n        self.nextEpisodePubDate = Date()\\n        self.feedUrl = podcast.feedURL ?? \\\"\\\"\\n    }\\n  }\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson2PodcastViewModelExplain4\\n// $$liquidcoderFilename-PodcastViewModel\\n\\nimport Foundation\\nimport FeedKit\\n\\nstruct PodcastViewModel: Identifiable, Equatable {\\n\\n    ...\\n\\n    static var placeholder: PodcastViewModel {\\n        createPlaceholder(id: 0)\\n    }\\n\\n    static var listPlaceholder: [PodcastViewModel] {\\n        (0..<10).map {\\n            createPlaceholder(id: $0)\\n        }\\n    }\\n\\n private static func createPlaceholder(id: Int) -> PodcastViewModel {...}\\n\\n    internal init(id: Int, title: String, publisher: String? = nil, imageUrl: String, thumbnailUrl: String? = nil, image: Data? = nil, thumbnail: Data? = nil, author: String? = nil, totalEpisodes: Int, explicitContent: String, description: String, date: String? = nil, language: String? = nil, country: String, genres: [String]? = nil, episodes: [EpisodeViewModel]? = nil, nextEpisodePubDate: Date, feedUrl: String, isBookmarked: Bool = false) { ... }\\n\\n    init(podcast: Podcast) { ... }\\n\\n// highlight-start\\n    init(_ feed: RSSFeed, existingPodcast: PodcastViewModel? = nil ) {\\n\\n        self.id = existingPodcast?.id ?? 0\\n        self.title = existingPodcast?.title ?? \\\"\\\"\\n        self.imageUrl = existingPodcast?.imageUrl ?? \\\"\\\"\\n        self.author = existingPodcast?.author ?? \\\"\\\"\\n        self.thumbnailUrl = existingPodcast?.thumbnailUrl ?? \\\"\\\"\\n        self.image = existingPodcast?.image ?? Data()\\n        self.thumbnail =  existingPodcast?.thumbnail ?? Data()\\n        self.totalEpisodes =  existingPodcast?.episodes.count ?? 0\\n        self.country = existingPodcast?.country ?? \\\"\\\"\\n        self.genres = existingPodcast?.genres ?? []\\n        self.feedUrl = existingPodcast?.feedUrl ?? \\\"\\\"\\n        self.isBookmarked = existingPodcast?.isBookmarked ?? false\\n        self.publisher = feed.managingEditor ?? \\\"\\\"\\n        self.explicitContent = feed.iTunes?.iTunesExplicit ?? \\\"\\\"\\n        self.description = feed.description ?? \\\"No description\\\"\\n        self.language = feed.language ?? \\\"\\\"\\n        self.nextEpisodePubDate = Date() \\n\\n        self.episodes = feed.items?.compactMap({ item  in\\n            EpisodeViewModel(feedItem: item, podcast: self)\\n        }) ?? []\\n        self.date = feed.pubDate.map { $0.format() } ?? \\\"\\\"\\n\\n        if imageUrl.isEmpty {\\n            if let imgUrl = feed.iTunes?.iTunesImage?.attributes?.href  {\\n                self.imageUrl = imgUrl\\n            }\\n        }\\n    }\\n\\n// highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson2PodcastViewModelExplain5\\n// $$liquidcoderFilename-PodcastViewModel\\n\\nimport Foundation\\nimport FeedKit\\n\\nstruct PodcastViewModel: Identifiable, Equatable {\\n\\n    ...\\n\\n    static var placeholder: PodcastViewModel {\\n        createPlaceholder(id: 0)\\n    }\\n\\n    static var listPlaceholder: [PodcastViewModel] {\\n        (0..<10).map {\\n            createPlaceholder(id: $0)\\n        }\\n    }\\n\\n private static func createPlaceholder(id: Int) -> PodcastViewModel {...}\\n\\n    internal init(id: Int, title: String, publisher: String? = nil, imageUrl: String, thumbnailUrl: String? = nil, image: Data? = nil, thumbnail: Data? = nil, author: String? = nil, totalEpisodes: Int, explicitContent: String, description: String, date: String? = nil, language: String? = nil, country: String, genres: [String]? = nil, episodes: [EpisodeViewModel]? = nil, nextEpisodePubDate: Date, feedUrl: String, isBookmarked: Bool = false) { ... }\\n\\n    init(podcast: Podcast) { ... }\\n\\n    init(_ feed: RSSFeed, existingPodcast: PodcastViewModel? = nil ) { ... }\\n\\n// highlight-start\\n    static func == (lhs: PodcastViewModel, rhs: PodcastViewModel) -> Bool {\\n            return lhs.id == rhs.id\\n                && lhs.language == rhs.language\\n                && lhs.episodes.count == rhs.episodes.count\\n                && lhs.image.count == rhs.image.count\\n                && lhs.thumbnail.count == rhs.thumbnail.count\\n}\\n// highlight-end\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson2Introduction-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey guys welcome back to lesson number 2. In the previous lesson, we started by setting up the project. We've also added app icons based on the type of environment we are running. In this one, we will add swiftLint to help us write better code as well as  define the model and view model, and talk a little bit about what services we will be using for this project. We will also install the package that we will need to parse \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"RSSFeed\")), \" . So let's get started.\")), mdx(\"div\", {\n    id: \"Lesson2SwiftLint-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"SwiftLint\"), mdx(\"p\", null, \"There are 2 ways one can install SwiftLint on mac os. The first one is by visiting their github release page and downloading the package directly from there and installing it like you would install any other program.\")), mdx(\"div\", {\n    id: \"Lesson2BrewInstall-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The other way is by using \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Homebrew\")), \" if you already have it  installed on your computer. \"), mdx(\"p\", null, \"You just need to run this command in your terminal\")), mdx(\"div\", {\n    id: \"Lesson2DownloadBrew-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"If you don't have \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Homebrew\")), \" installed on your computer, just vist \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://brew.sh/\"\n  }), \"https://brew.sh\"), \", and follow the instruction from there. I will also add this link in the course documentation. \")), mdx(\"div\", {\n    id: \"Lesson2AddSwiftLint-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Adding SwiftLint in the project\"), mdx(\"p\", null, \"Adding swiftlnt in Xcode is a straight forward process. We just need to add a  \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"\\\"Run Script Phase\\\"\")), \" in the build phases tab in Xcode for each target we have except the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"watchOS\"), \" one. Here is how we will do this:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Select the project\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Select the first target which is the iOS target for me.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Select the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"build phases\"), \" tab\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Click on the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"+\"), \" icon\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Select the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"\\\"New Run Script Phase\\\"\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Now expand \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"\\\"Run Script\\\"\"), \" section, add this script.\"))), mdx(\"div\", {\n    id: \"Lesson2FinishAddSwiftLint-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"You should have something like in the picture\"), mdx(\"p\", null, \"Now do the same thing for the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"macOS\"), \" and \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"watchOS Extension.\")), mdx(\"p\", null, \"After you've added the script to other targets, try to build the project and you will get a bunch of warnings.\")), mdx(\"div\", {\n    id: \"Lesson2SwiftLintRuleMod-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Modifying swiftLint rules\"), mdx(\"p\", null, \"Right now we are getting some strange warnings, likely for us we can customise how swiftLint reports warnings and errors. Just create a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".swiftlint.yml\"), \" file in the root project. \"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Select the project\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"New file\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Choose Empty in file template\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Name the file \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \".swiftlint.yml\"), \" and make sure the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"macOS\"), \" and \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"watchOS Extension\"), \" target are checked.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"You might get a warning saying that a file starting with \\\".\\\" will be hidden by the system, click \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Use \\\".\\\"\"), \" to continue.\"))), mdx(\"div\", {\n    id: \"Lesson2AddSwiftLintRulesCode-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next put the following code inside.\"), mdx(\"p\", null, \"Save the file and build the project (CMD + B). You should see fewer warnings now. You can change those values to your liking, and even add to them if need be. Checkout the entire list of rules you may need to change here \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://realm.github.io/SwiftLint/rule-directory.html\"\n  }), \"https://realm.github.io/SwiftLint/rule-directory.html\"), \".\")), mdx(\"div\", {\n    id: \"Lesson2PodcastRestApiIntro-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Podcast rest api\"), mdx(\"p\", null, \"Finding a podcast rest service provider was one of the hard part when preparing this course, and because of that we will not have the subscription functionality as we will be using the iTunes search service to fetch podcasts.\"), mdx(\"p\", null, \"There was another service, the only service really, that was providing a podcast rest api. It has a free tier, but if you want to use it in production, you would need to upgrade to the paying version. I wanted to use that, in fact I even started using it, until I read their terms of services, and then I stopped. In short, they prohibit to use their rest API when teaching a lot of people. However, if you want to try it out or use it in production here is the link\\xA0\", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://www.listennotes.com/api/\"\n  }), \"https://www.listennotes.com/api/\"), \".\")), mdx(\"div\", {\n    id: \"Lesson2PodcastRestApiIntro2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Like I said we will use the iTunes rest api.So, Let's talk about that.\"), mdx(\"p\", null, \"The api is easy to use. The base url is\\xA0\\xA0\", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://itunes.apple.com/search?media=podcast&\"\n  }), \"**https://itunes.apple.com/search?entity=podcast\"), \"\\xA0,** so you just append another query parameter and perform the search. The result returned will in json format, but it's not all. The result we will get from running a get request to the above url will be missing a few details crucial to the success of what we are trying to accomplish. For example, the result will not contain episodes. Fortunately, it will contain a field with which we will be able to retrieve more details about a particular podcast, and this field is an RSS url of the podcast.\")), mdx(\"div\", {\n    id: \"Lesson2PodcastRestApiIntro3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"So every time we want to see more details about a particular podcast, we will need to parse its RSS feed which we will do using the help of an external package, and it's the only external package we will use in the project. Let's now add that package.\")), mdx(\"div\", {\n    id: \"Lesson2FeedKitIntro-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"FeedKit\"), mdx(\"p\", null, \"We will use the swift package manager to add the package to the project.  Go to this page if you want to use another type of installation \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://github.com/nmdias/FeedKit/blob/master/INSTALL.md\"\n  }), \"https://github.com/nmdias/FeedKit/blob/master/INSTALL.md\"), \".\")), mdx(\"div\", {\n    id: \"Lesson2FeedKitIntro2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In Xcode:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Click the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"File\"), \" menu\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Swift Packages\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Add Package Dependency.\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Copy this \", mdx(\"a\", _extends({\n    parentName: \"li\"\n  }, {\n    \"href\": \"https://github.com/nmdias/FeedKit.git\"\n  }), \"https://github.com/nmdias/FeedKit.git\"), \", and paste in the search field presented to you, then click \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Next.\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Leave the version as is, and click \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Next\"), \" again. The download will take a couple of seconds\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"After the download process, make sure FeedKit is checked and click \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Finish.\"), \" \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The package was added to the iOS target only, let's add to the remaining ones now. Open the project settings, and under \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"General,\"), \" click the \\\"+\\\"  in the \\\"\", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Frameworks\"), \", \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Libraries\"), \" and \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Embedded Content\\\"\"), \" section. Choose the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"FeedKit\"), \" under FeedKit Package and click \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Add.\"), \" Do this for \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"macOS\"), \" and \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"watchOS Extension.\"))), mdx(\"p\", null, \"With that in place, let's now start writing some code. We will start by defining our \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Models\"), \" and \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"ViewModels\"))), mdx(\"div\", {\n    id: \"Lesson2Models1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Models\"), mdx(\"p\", null, \"Create a folder in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Shared\"), \" folder, and name it \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Models.\"), \" Now create a new swift file named Podcast, make sure all targets are checked except the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"watchOS\"), \" one.\")), mdx(\"div\", {\n    id: \"Lesson2PodcastModels-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Add the following inside.\"), mdx(\"p\", null, \"This is our \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastList\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Podcast\"), \" models, this is the types that we will parse the data coming from the network into.\")), mdx(\"div\", {\n    id: \"Lesson2PodcastModels2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"If you are wondering why we need the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastList\"), \",  here is why: The json returned will be an object with  2 entries, a  resultCount (which is the number of podcast objects returned) and the actual array of podcasts. Here is what that looks like:\")), mdx(\"div\", {\n    id: \"Lesson2PodcastModels3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Beside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Podcast\"), \" model, we will also need genres. I have include a genres.json file containing a list of genres in json format that we will parse and presented for to the user. Of course the user can also search for whatever he wants, but these genres will be espacially useful  on the apple watch as it's not that fun typing on it.\")), mdx(\"div\", {\n    id: \"Lesson2AddGenreModel1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"So, Inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Models\"), \" folder, add the following inside:\")), mdx(\"div\", {\n    id: \"Lesson2AddGenreModel2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Just like the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Podcast\"), \"  model, the Genre has the same shape. Here is what the json looks like\"), mdx(\"p\", null, \"We are done with the Models for now, we will certainly come back and add some more, but right now let us create the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ViewModels\"), \".\"))), mdx(\"div\", {\n    id: \"Lesson2ViewModelsIntro-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"ViewModels\"), mdx(\"p\", null, \"Create a folder inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Shared\"), \" one named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"ViewModels.\"), \"  Now inside this one, add a new. swift file, and name it \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"GenreViewModel.swift\"))), mdx(\"div\", {\n    id: \"Lesson2EpisodeViewModel-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next, let's add the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"EpisodeViewModel.\"), \" In the same folder, add a swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"EpisodeViewModel.\"))), mdx(\"div\", {\n    id: \"Lesson2EpisodeViewModelExplain1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We first import \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"FeedKit\"), \" as we will use it in a moment.\\nThen we declare properties of the model, we also need to keep track of the parent podcast which we declared last.\")), mdx(\"div\", {\n    id: \"Lesson2EpisodeViewModelExplain2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next we declare placeholders to be used when showing  previews and redacted views while content from the network of database is loading.\")), mdx(\"div\", {\n    id: \"Lesson2EpisodeViewModelExplain3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We also generate an internal initialiser in order to create our placeholders.\")), mdx(\"div\", {\n    id: \"Lesson2EpisodeViewModelExplain4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And last we create another overload of the initialiser which takes in a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RSSFeedItem\"), \" and a podcast view model in order to create a new instance of the struct.\")), mdx(\"div\", {\n    id: \"Lesson2ViewModelsErrors-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Don't mind the errors, we will create the missing bit  later. Let's finish this lesson with the create of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastViewModel\"), \". Create a new swift file inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"ViewModels,\"), \" and name it \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"PodcastViewModel.\"), \" The content of this file is huge but I'll paste it here anyway.\")), mdx(\"div\", {\n    id: \"Lesson2PodcastViewModelExplain1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastViewModel\"), \" is similar to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeViewModel\"), \" with some addition that I am about to talk about in a bit. Read on.\")), mdx(\"div\", {\n    id: \"Lesson2PodcastViewModelExplain4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The first overload initialiser takes in a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Podcast\"), \" model, and the second takes in an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RSSFeed\"), \" and an existing podcast view model, this way we can update the current model while keeping the old values. \"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"This wouldn't be the case if we were using classes, but using them comes with another problem of mutability, hence keeping the use of struct is safer as we would not worry about state inconsistencies at all.\"))), mdx(\"div\", {\n    id: \"Lesson2PodcastViewModelExplain5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Last we implement the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Equatable\"), \" \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"==\"), \" because the model conforms to the   \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Equatable\"), \"  protocol. We have conform to this protocol in order to listen for updates with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".onChange\"), \" modifier .\"), mdx(\"p\", null, \"This lesson is getting too long, so we will fix the errors we have in the next one. Happy coding!\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#swiftlint","title":"SwiftLint"},{"url":"#adding-swiftlint-in-the-project","title":"Adding SwiftLint in the project"},{"url":"#modifying-swiftlint-rules","title":"Modifying swiftLint rules"},{"url":"#podcast-rest-api","title":"Podcast rest api"},{"url":"#feedkit","title":"FeedKit"},{"url":"#models","title":"Models"},{"url":"#viewmodels","title":"ViewModels"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Hey guys welcome back to lesson number 2. In the previous lesson, we started by setting up the project. We've also added app icons based on the type of environment we are running. In this one, we will add swiftLint to help us write better code as well as  define the model and view model, and talk a little bit about what services we will be using for this project. We will also install the package that we will need to parse \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"RSSFeed\")), \" . So let's get started.\"), mdx(\"h2\", null, \"SwiftLint\"), mdx(\"p\", null, \"There are 2 ways one can install SwiftLint on mac os. The first one is by visiting their github release page and downloading the package directly from there and installing it like you would install any other program.\"), mdx(\"p\", null, \"The other way is by using \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Homebrew\")), \" if you already have it  installed on your computer. You just need to run this command in your terminal:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-bash\"\n  }), \"brew install swiftlint\\n\")), mdx(\"p\", null, \"If you don't have \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Homebrew\")), \" installed on your computer, just vist \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://brew.sh/\"\n  }), \"https://brew.sh\"), \", and follow the instruction from there. I will also add this link in the course documentation. \"), mdx(\"h2\", null, \"Adding SwiftLint in the project\"), mdx(\"p\", null, \"Adding swiftlnt in Xcode is a straight forward process. We just need to add a  \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"\\\"Run Script Phase\\\"\")), \" in the build phases tab in Xcode for each target we have except the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"watchOS\"), \" one. Here is how we will do this:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Select the project\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Select the first target which is the iOS target for me.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Select the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"build phases\"), \" tab\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Click on the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"+\"), \" icon\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Select the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"\\\"New Run Script Phase\\\"\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Now expand \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"\\\"Run Script\\\"\"), \" section, add this script.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-bash\"\n  }), \"if which swiftlint >/dev/null; then\\n  swiftlint\\nelse\\n  echo \\\"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\\\"\\nfi\\n\")), mdx(\"p\", null, \"You should have something like:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610200593/StreamCast/idfs8efi2gke8seqkptl.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610200593/StreamCast/idfs8efi2gke8seqkptl.png\"\n  }))), mdx(\"p\", null, \"Now do the same thing for the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"macOS\"), \" and \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"watchOS Extension.\")), mdx(\"p\", null, \"After you've added the script to other targets, try to build the project and you will get a bunch of warnings. I count 42 in my project.\"), mdx(\"h2\", null, \"Modifying swiftLint rules\"), mdx(\"p\", null, \"Right now we are getting some strange warnings, likely for us we can customise how swiftLint reports warnings and errors. Just create a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".swiftlint.yml\"), \" file in the root project. \"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Select the project\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"New file\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Choose Empty in file template\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Name the file \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \".swiftlint.yml\"), \" and make sure the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"macOS\"), \" and \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"watchOS Extension\"), \" target are checked.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"You might get a warning saying that a file starting with \\\".\\\" will be hidden by the system, click \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Use \\\".\\\"\"), \" to continue.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Next put the following code inside.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-bash\"\n  }), \"disabled_rules:\\n- trailing_whitespace\\n\\nopt_in_rules:\\n- empty_count\\n- empty_string\\n\\nexcluded:\\n- Carthage\\n- Pods\\n- SwiftLint/Common/3rdPartyLib\\n\\nline_length:\\n    warning: 150\\n    error: 200\\n    ignores_function_declarations: true\\n    ignores_comments: true\\n    ignores_urls: true\\n\\nfunction_body_length:\\n    warning: 300\\n    error: 500\\n\\nfunction_parameter_count:\\n    warning: 5\\n    error: 7\\n\\ntype_body_length:\\n    warning: 300\\n    error: 500\\n\\nfile_length:\\n    warning: 1000\\n    error: 1500\\n    ignore_comment_only_lines: true\\n\\ncyclomatic_complexity:\\n    warning: 15\\n    error: 25\\n\\nreporter: \\\"xcode\\\"\\n\")), mdx(\"p\", null, \"Save the file and build the project (CMD + B). You should see fewer warnings now. You can change those values to your liking, and even add to them if need be. Checkout the entire list of rules you may need to change here \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://realm.github.io/SwiftLint/rule-directory.html\"\n  }), \"https://realm.github.io/SwiftLint/rule-directory.html\"), \".\"), mdx(\"h2\", null, \"Podcast rest api\"), mdx(\"p\", null, \"Finding a podcast rest service provider was  one of the hard part when preparing this course, and because of that we will not have the subscription functionality as we will be using the iTunes search service to fetch podcasts.\"), mdx(\"p\", null, \"There was another service, the only service really, that was providing a podcast rest api. It has a free tier, but if you want to use it in production, you would need to upgrade to the paying version. I wanted to use that, in fact I even started using it, until I read their terms of services, and then I stopped. In short, they prohibit to use their rest API when teaching a lot of people. However, if you want to try it out or use it in production here is the link \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://www.listennotes.com/api/\"\n  }), \"https://www.listennotes.com/api/\"), \".\"), mdx(\"p\", null, \"Like I said we will use the iTunes rest api.So,  Let's talk about that.\"), mdx(\"p\", null, \"The api is  easy to use. The base url is  \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://itunes.apple.com/search?media=podcast&\"\n  }), \"**https://itunes.apple.com/search?entity=podcast\"), \" ,**  so you just append another query parameter and perform the search. The result returned will in json format, but it's not all. The result we will get from running a get request to the above url will be missing a few details crucial to the success of what we are trying to accomplish. For example, the result will not contain episodes. Fortunately, it will contain a field with which we will be able to retrieve more details about a particular podcast, and this field is an RSS url of the podcast.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610886845/StreamCast/rgcadomz61hcxsd4splz.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610886845/StreamCast/rgcadomz61hcxsd4splz.png\"\n  }))), mdx(\"p\", null, \"So every time we want to see more details about a particular podcast, we will need to parse its RSS feed which we will do using the help of an external package, and it's the only external package we will use in the project. Let's now add that package.\"), mdx(\"h2\", null, \"FeedKit\"), mdx(\"p\", null, \"We will use the swift package manager to add the package to the project.  Go to this page if you want to use another type of installation \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://github.com/nmdias/FeedKit/blob/master/INSTALL.md\"\n  }), \"https://github.com/nmdias/FeedKit/blob/master/INSTALL.md\"), \".\"), mdx(\"p\", null, \"In Xcode:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Click the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"File\"), \" menu\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Swift Packages\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Add Package Dependency.\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Copy this \", mdx(\"a\", _extends({\n    parentName: \"li\"\n  }, {\n    \"href\": \"https://github.com/nmdias/FeedKit.git\"\n  }), \"https://github.com/nmdias/FeedKit.git\"), \", and paste in the search field presented to you, then click \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Next.\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Leave the version as is, and click \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Next\"), \" again. The download will take a couple of seconds\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"After the download process, make sure FeedKit is checked and click \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Finish.\"), \" \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The package was added to the iOS target only, let's add to the remaining ones now. Open the project settings, and under \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"General,\"), \" click the \\\"+\\\"  in the \\\"\", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Frameworks\"), \", \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Libraries\"), \" and \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Embedded Content\\\"\"), \" section. Choose the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"FeedKit\"), \" under FeedKit Package and click \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Add.\"), \" Do this for \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"macOS\"), \" and \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"watchOS Extension.\"))), mdx(\"p\", null, \"With that in place, let's now start writing some code. We will start by defining our \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Models\"), \" and \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"ViewModels\")), mdx(\"h2\", null, \"Models\"), mdx(\"p\", null, \"Create a folder in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Shared\"), \" folder, and name it \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Models.\"), \" Now create a new swift file named Podcast, make sure all targets are checked except the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"watchOS\"), \" one.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Podcast.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nstruct PodcastList: Codable {\\n    let resultCount: Int\\n    let results: [Podcast]\\n}\\n\\nstruct Podcast: Codable {\\n    var artistID: Int?\\n    var collectionID, trackID: Int\\n    var description: String?\\n    var artistName, collectionName, trackName: String?\\n    var feedURL: String?\\n    var artworkUrl30, artworkUrl60, artworkUrl100: String?\\n    var releaseDate: String?\\n    var collectionExplicitness, trackExplicitness: String?\\n    var trackCount: Int?\\n    var country, primaryGenreName: String?\\n    var contentAdvisoryRating: String?\\n    var artworkUrl600: String?\\n    var genres: [String]?\\n\\n    enum CodingKeys: String, CodingKey {\\n        case artistID = \\\"artistId\\\"\\n        case collectionID = \\\"collectionId\\\"\\n        case trackID = \\\"trackId\\\"\\n        case artistName, collectionName, trackName\\n        case feedURL = \\\"feedUrl\\\"\\n        case artworkUrl30, artworkUrl60, artworkUrl100\\n        case releaseDate, collectionExplicitness, trackExplicitness, trackCount, country, primaryGenreName, contentAdvisoryRating, artworkUrl600\\n        case genres\\n    }\\n\\n}\\n\")), mdx(\"p\", null, \"This is our \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastList\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Podcast\"), \" models, this is the types that we will parse the data coming from the network into. If you are wondering why we need the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastList\"), \",  here is why: The json returned will be an object with  2 entries, a  resultCount (which is the number of podcast objects returned) and the actual array of podcasts. Here is what that looks like:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-json\"\n  }), \"{\\n    \\\"resultCount\\\": 50,\\n    \\\"results\\\": [{...},{...}, ...]\\n}\\n\")), mdx(\"p\", null, \"Beside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Podcast\"), \" model, we will also need genres. I have include a genres.json file containing a list of genres in json format that we will parse and presented for to the user. Of course the user can also search for whatever he wants, but these genres will be espacially useful  on the apple watch as it's not that fun typing on it.\"), mdx(\"p\", null, \"So, Inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Models\"), \" folder, add the following inside:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Genre.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct GenreList: Codable {\\n    let genres: [Genre]\\n}\\n\\nstruct Genre: Codable {\\n    let id: Int\\n    let name: String\\n}\\n\")), mdx(\"p\", null, \"Just like the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Podcast\"), \"  model, the Genre has the same shape. Here is what the json looks like\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-json\"\n  }), \"{\\n    \\\"genres\\\": [\\n        {\\n            \\\"id\\\": 144,\\n            \\\"name\\\": \\\"Finance\\\",\\n        },\\n            ...\\n        ]\\n}\\n\")), mdx(\"p\", null, \"We are done with the Models for now, we will certainly come back and add some more, but right now let us create the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ViewModels\"), \".\")), mdx(\"h2\", null, \"ViewModels\"), mdx(\"p\", null, \"Create a folder inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Shared\"), \" one named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"ViewModels.\"), \"  Now inside this one, add a new. swift file, and name it \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"GenreViewModel.swift\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreViewModel.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct GenreViewModel: Identifiable, Hashable {\\n    var id: Int\\n    var name: String\\n\\n    static let placeholder = createPlaceholder(id: 1)\\n\\n    static var listPlaceholders: [GenreViewModel] {\\n        (0..<15).map {\\n            createPlaceholder(id: $0)\\n        }\\n    }\\n\\n    private static func createPlaceholder(id: Int) -> GenreViewModel {\\n        GenreViewModel( Genre(id: id, name: \\\"Technology\\\") )\\n    }\\n\\n    init(_ genre: Genre) {\\n        self.id = genre.id\\n        self.name = genre.name\\n    }\\n}\\n\")), mdx(\"p\", null, \"Next, let's add the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"EpisodeViewModel.\"), \" In the same folder, add a swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"EpisodeViewModel.\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"EpisodeViewModel.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\nimport FeedKit\\n\\nstruct EpisodeViewModel: Identifiable {\\n\\n    let id = UUID()\\n    var title, description: String\\n    var date: String\\n    var audioUrl: String\\n    var audio: Data?\\n    var audioLength: Double\\n    var audioSize: String\\n    var podcast: PodcastViewModel = .placeholder\\n\\n    static var placeholder: EpisodeViewModel {\\n        createPlaceholder()\\n    }\\n\\n    static var listPlaceholder: [EpisodeViewModel] {\\n        (0..<10).map { _ in\\n            createPlaceholder()\\n        }\\n    }\\n\\n    private static func createPlaceholder() -> EpisodeViewModel {\\n        EpisodeViewModel(title: \\\"Lorem ipsum\\\",\\n                                description: \\\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\\\",\\n                                date: \\\"20 sept 2020\\\",\\n                                audio: \\\"\\\",\\n                                audioLength: 120,\\n                                audioSize: \\\"13mb\\\",\\n                                podcast: PodcastViewModel.placeholder)\\n    }\\n\\n    internal init(title: String, description: String, date: String, audio: String, audioLength: Double, audioSize: String, podcast: PodcastViewModel) {\\n        self.title = title\\n        self.description = description\\n        self.date = date\\n        self.audioUrl = audio\\n        self.audioLength = audioLength\\n        self.audioSize = audioSize\\n        self.podcast = podcast\\n    }\\n\\n    init(feedItem: RSSFeedItem, podcast: PodcastViewModel) {\\n        self.title = feedItem.title ?? \\\"\\\"\\n        self.description = feedItem.description ?? \\\"\\\"\\n        self.date = feedItem.pubDate.map { $0.format() } ?? \\\"\\\"\\n        self.audioUrl = feedItem.enclosure?.attributes?.url ?? \\\"\\\"\\n        self.audioLength = feedItem.iTunes?.iTunesDuration ?? 0\\n\\n        self.audioSize = feedItem.enclosure?.attributes?.length.map{\\n            \\\"\\\\($0 / 1000000) mb\\\"\\n        } ?? \\\"0 mb\\\"\\n\\n        self.podcast = podcast\\n    }\\n}\\n\")), mdx(\"p\", null, \"Here is the rundown of what I have done above:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We first import \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"FeedKit\"), \" as we will use it in a moment.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then we declare properties of the model, we also need to keep track of the parent podcast which we declared last.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Next we declare placeholders to be used when showing  previews and redacted views while content from the network of database is loading.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We also generate an internal initialiser in order to create our placeholders.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"And last we create another overload of the initialiser which takes in a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"RSSFeedItem\"), \" and a podcast view model in order to create a new instance of the struct.\")), mdx(\"p\", null, \"Don't mind the errors, we will create the missing bit  later. Let's finish this lesson with the create of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastViewModel\"), \". Create a new swift file inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"ViewModels,\"), \" and name it \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"PodcastViewModel.\"), \" The content of this file is huge but I'll paste it here anyway.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"PodcastViewModel.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\nimport FeedKit\\n\\nstruct PodcastViewModel: Identifiable, Equatable {\\n\\n    var id: Int\\n    var title: String\\n    var publisher: String = \\\"\\\"\\n    var imageUrl: String\\n    var thumbnailUrl: String = \\\"\\\"\\n    var image = Data()\\n    var thumbnail = Data()\\n    var author: String = \\\"\\\"\\n    var totalEpisodes: Int\\n    var explicitContent: String\\n    var description: String\\n    var date: String = \\\"\\\"\\n    var language: String?\\n    var country: String\\n    var genres: [String] = []\\n    var episodes: [EpisodeViewModel] = []\\n    var nextEpisodePubDate: Date\\n    var feedUrl: String\\n    var isBookmarked = false\\n\\n    static var placeholder: PodcastViewModel {\\n        createPlaceholder(id: 0)\\n    }\\n\\n    static var listPlaceholder: [PodcastViewModel] {\\n        (0..<10).map {\\n            createPlaceholder(id: $0)\\n        }\\n    }\\n\\n    private static func createPlaceholder(id: Int) -> PodcastViewModel {\\n        PodcastViewModel(id: id,\\n                         title: \\\"Lorem ipsum\\\",\\n                         imageUrl: \\\"\\\",\\n                         totalEpisodes: 23,\\n                         explicitContent: \\\"No\\\",\\n                         description: \\\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\\\",\\n                         country: \\\"US\\\",\\n                         nextEpisodePubDate: Date() ,\\n                         feedUrl: \\\"\\\")\\n    }\\n\\n    internal init(id: Int, title: String, publisher: String? = nil, imageUrl: String, thumbnailUrl: String? = nil, image: Data? = nil, thumbnail: Data? = nil, author: String? = nil, totalEpisodes: Int, explicitContent: String, description: String, date: String? = nil, language: String? = nil, country: String, genres: [String]? = nil, episodes: [EpisodeViewModel]? = nil, nextEpisodePubDate: Date, feedUrl: String, isBookmarked: Bool = false) {\\n        self.id = id\\n        self.title = title\\n        self.publisher = publisher ?? \\\"\\\"\\n        self.imageUrl = imageUrl\\n        self.thumbnailUrl = thumbnailUrl ?? \\\"\\\"\\n        self.image = image ?? Data()\\n        self.thumbnail = thumbnail ?? Data()\\n        self.author = author ?? \\\"\\\"\\n        self.totalEpisodes = totalEpisodes\\n        self.explicitContent = explicitContent\\n        self.description = description\\n        self.date = date ?? \\\"\\\"\\n        self.language = language\\n        self.country = country\\n        self.genres = genres ?? []\\n        self.episodes = episodes ?? []\\n        self.nextEpisodePubDate = nextEpisodePubDate\\n        self.feedUrl = feedUrl\\n        self.isBookmarked = isBookmarked\\n    }\\n\\n    init(podcast: Podcast) {\\n        self.id = podcast.collectionID\\n        self.title = podcast.collectionName ?? \\\"\\\"\\n        self.publisher = \\\"\\\"\\n        self.imageUrl = podcast.artworkUrl600 ?? \\\"\\\"\\n        self.thumbnailUrl = podcast.artworkUrl100 ?? \\\"\\\"\\n        self.author = podcast.artistName ?? \\\"\\\"\\n        self.totalEpisodes = podcast.trackCount ?? 0\\n        self.explicitContent = podcast.collectionExplicitness ?? \\\"\\\"\\n        self.description = podcast.description ?? \\\"\\\"\\n        self.date = podcast.releaseDate?.formatDate() ?? \\\"\\\"\\n        self.language = nil\\n        self.country = podcast.country ?? \\\"\\\"\\n        self.genres = podcast.genres ?? []\\n        self.episodes = []\\n        self.nextEpisodePubDate = Date()\\n        self.feedUrl = podcast.feedURL ?? \\\"\\\"\\n    }\\n\\n    init(_ feed: RSSFeed, existingPodcast: PodcastViewModel? = nil ) {\\n\\n        self.id = existingPodcast?.id ?? 0\\n        self.title = existingPodcast?.title ?? \\\"\\\"\\n        self.imageUrl = existingPodcast?.imageUrl ?? \\\"\\\"\\n        self.author = existingPodcast?.author ?? \\\"\\\"\\n        self.thumbnailUrl = existingPodcast?.thumbnailUrl ?? \\\"\\\"\\n        self.image = existingPodcast?.image ?? Data()\\n        self.thumbnail =  existingPodcast?.thumbnail ?? Data()\\n        self.totalEpisodes =  existingPodcast?.episodes.count ?? 0\\n        self.country = existingPodcast?.country ?? \\\"\\\"\\n        self.genres = existingPodcast?.genres ?? []\\n        self.feedUrl = existingPodcast?.feedUrl ?? \\\"\\\"\\n        self.isBookmarked = existingPodcast?.isBookmarked ?? false\\n        self.publisher = feed.managingEditor ?? \\\"\\\"\\n        self.explicitContent = feed.iTunes?.iTunesExplicit ?? \\\"\\\"\\n        self.description = feed.description ?? \\\"No description\\\"\\n        self.language = feed.language ?? \\\"\\\"\\n        self.nextEpisodePubDate = Date() \\n\\n        self.episodes = feed.items?.compactMap({ item  in\\n            EpisodeViewModel(feedItem: item, podcast: self)\\n        }) ?? []\\n        self.date = feed.pubDate.map { $0.format() } ?? \\\"\\\"\\n\\n        if imageUrl.isEmpty {\\n            if let imgUrl = feed.iTunes?.iTunesImage?.attributes?.href  {\\n                self.imageUrl = imgUrl\\n            }\\n        }\\n    }\\n\\n    static func == (lhs: PodcastViewModel, rhs: PodcastViewModel) -> Bool {\\n        return lhs.id == rhs.id\\n            && lhs.language == rhs.language\\n            && lhs.episodes.count == rhs.episodes.count\\n            && lhs.image.count == rhs.image.count\\n            && lhs.thumbnail.count == rhs.thumbnail.count\\n    }\\n}\\n\")), mdx(\"p\", null, \"Here is a rundown of what the above code does:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We first import \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"FeedKit\"), \" as we will use it in a moment just like we did in the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"EpisodeViewModel\"), \" file.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then we declare properties of the model.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Next we declare placeholders to be used when showing  preview and redacted view while content from the network or database is loading.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We also generate an internal initialiser in order to create our placeholders.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The first overload initialiser takes in a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Podcast\"), \" model, and the second takes in an \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"RSSFeed\"), \" and an existing podcast view model, this way we can update the current model while keeping the old values. \")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"This wouldn't be the case if we were using classes, but using them comes with  another problem of mutability, hence keeping the use of struct is safer as we would not worry about state inconsistencies at all.\")), mdx(\"ol\", {\n    \"start\": 6\n  }, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Last we implement the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Equatable\"), \" \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"==\"), \" because the model conforms to the   \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Equatable\"), \"  protocol. We have conform to this protocol in order to listen for updates with \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \".onChange\"), \" modifier.\")), mdx(\"p\", null, \"This lesson is getting too long, so we will fix those errors we have in the next one. Happy coding!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#swiftlint","title":"SwiftLint"},{"url":"#adding-swiftlint-in-the-project","title":"Adding SwiftLint in the project"},{"url":"#modifying-swiftlint-rules","title":"Modifying swiftLint rules"},{"url":"#podcast-rest-api","title":"Podcast rest api"},{"url":"#feedkit","title":"FeedKit"},{"url":"#models","title":"Models"},{"url":"#viewmodels","title":"ViewModels"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-01-21T16:51:53.429Z","isNewLayout":true,"previews":null,"slug":"lesson-3-api-service","title":"Lesson #3: Api service","updatedDate":"2021-01-21T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZbzemXZfFYlwAnBTRyBmbTnSCrrKmGdMtoV","type":"zip","id":"54e835d8-e789-5051-9d4f-2d3245000c59","name":"Source code"},{"url":"https://u.pcloud.link/publink/show?code=XZV5shXZBklcRMideNb4cMCKyXli2Rs84MQ7","type":"zip","id":"e0b1ac30-93d5-5e12-b4d8-7ef008d6d207","name":"Genres.json"}],"allChapters":["Lesson3Introduction: Introduction","Lesson3DateExtensions1: Extensions","Lesson3InternetMonitor1: Monitoring Internet connexion status","Lesson3ApiService1: Api Service"],"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson3FixingErrors1\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610203887/StreamCast/mlbtw8tn4g9ljcx24cf9.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610203887/StreamCast/mlbtw8tn4g9ljcx24cf9.png\"\n  })))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3DateExtensions1\\n// $$liquidcoderFilename-Date+\\n\\nextension Date {\\n    func format(_ format: String = \\\"d MMM yyyy\\\") -> String {\\n           let formatter = DateFormatter()\\n           formatter.dateFormat = format\\n            formatter.locale = Locale.autoupdatingCurrent\\n           let component = formatter.string(from: self)\\n           return component\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3StringExtensions1\\n// $$liquidcoderFilename-String+\\n\\nimport Foundation\\n\\nextension String {\\n\\n    private func createFormatter() -> DateFormatter {\\n\\n        let formatter = DateFormatter()\\n        formatter.locale =  .autoupdatingCurrent\\n        formatter.dateFormat = \\\"yyyy-MM-dd'T'HH:mm:ssZ\\\"\\n\\n        return formatter\\n    }\\n\\n    func formatDate() -> String {\\n        let formatter = createFormatter()\\n        if let date = formatter.date(from: self) {\\n            return date.format()\\n        }\\n\\n        return \\\"\\\"\\n    }\\n\\n    func toDate() -> Date {\\n        let formatter = createFormatter()\\n        if let date = formatter.date(from: self) {\\n            return date\\n        }\\n\\n        return Date()\\n    }\\n\\n}\\n\")), mdx(\"div\", {\n    id: \"Lesson3ModifyingSwiftLintRules3\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610204789/StreamCast/jtavjuxbaiccs0xhbwmt.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610204789/StreamCast/jtavjuxbaiccs0xhbwmt.png\"\n  })))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3ModifyingSwiftLintRules4\\n\\nidentifier_name:\\n    min_length: 2\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3InternetMonitor2\\n// $$liquidcoderFilename-InternetMonitor\\n\\nimport Foundation\\n\\nclass InternetMonitor: ObservableObject {\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3InternetMonitor3\\n// $$liquidcoderFilename-InternetMonitor\\n\\nimport Foundation\\n// highlight-start\\nimport Network\\n// highlight-end\\n\\nclass InternetMonitor: ObservableObject {\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3InternetMonitor4\\n// $$liquidcoderFilename-InternetMonitor\\n\\nimport Foundation\\nimport Network\\n\\nclass InternetMonitor: ObservableObject {\\n\\n    // highlight-start\\n    private let monitor = NWPathMonitor()\\n    // highlight-end\\n\\n// highlight-start\\n    @Published var isAvailable = true\\n// highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3InternetMonitor5\\n// $$liquidcoderFilename-InternetMonitor\\n\\nimport Foundation\\nimport Network\\n\\nclass InternetMonitor: ObservableObject {\\n\\n    private let monitor = NWPathMonitor()\\n\\n    @Published var isAvailable = true\\n\\n        // highlight-start\\n        init() {\\n\\n        let queue = DispatchQueue(label: \\\"com.liquidcoder.StreamCast.InternetMonitor\\\")\\n        monitor.pathUpdateHandler = { path in\\n            DispatchQueue.main.async {\\n\\n              self.isAvailable = path.status == .satisfied\\n            }\\n        }\\n\\n    }\\n    // highlight-start\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3InternetMonitor6\\n// $$liquidcoderFilename-InternetMonitor\\n\\nimport Foundation\\nimport Network\\n\\nclass InternetMonitor: ObservableObject {\\n\\n    private let monitor = NWPathMonitor()\\n\\n    @Published var isAvailable = true\\n\\n        init() {\\n\\n        let queue = DispatchQueue(label: \\\"com.liquidcoder.StreamCast.InternetMonitor\\\")\\n        monitor.pathUpdateHandler = { path in\\n            DispatchQueue.main.async {\\n\\n              self.isAvailable = path.status == .satisfied\\n            }\\n        }\\n\\n    // highlight-start\\n        monitor.start(queue: queue)\\n        // highlight-end\\n\\n    }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3InternetMonitor7\\n// $$liquidcoderFilename-ContentView\\n\\nimport SwiftUI\\n\\nstruct ContentView: View {\\n\\n    @StateObject private var internetMonitor = InternetMonitor()\\n\\n    var body: some View {\\n            Text( internetMonitor.isAvailable ? \\\"Connected!\\\" : \\\"Not connected\\\")\\n                .bold()\\n                .foregroundColor( internetMonitor.isAvailable ? .green : .red)\\n\\n    }\\n}\\n\\nstruct ContentView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        ContentView()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3ParsingConfigs1\\n\\nBASE_URL = https:/$()/itunes.apple.com/search\\n\")), mdx(\"div\", {\n    id: \"Lesson3ParsingConfigs2\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610205550/StreamCast/dzjf6h9mnd1pmdgroic5.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610205550/StreamCast/dzjf6h9mnd1pmdgroic5.png\"\n  })))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3ParsingConfigs3\\n// $$liquidcoderFilename-ConfigParser\\n\\nimport Foundation\\n\\n    // highlight-start\\npublic enum ConfigParser {\\n\\n}\\n    // highlight-end\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3ParsingConfigs4\\n// $$liquidcoderFilename-ConfigParser\\n\\nimport Foundation\\n\\npublic enum ConfigParser {\\n\\n// highlight-start\\n enum Keys {\\n    static let baseUrl = \\\"BASE_URL\\\"\\n }\\n    // highlight-end    \\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3ParsingConfigs5\\n// $$liquidcoderFilename-ConfigParser\\n\\nimport Foundation\\n\\npublic enum ConfigParser {\\n\\n enum Keys {\\n    static let baseUrl = \\\"BASE_URL\\\"\\n }\\n\\n// highlight-start\\n    private static let infoDictionary: [String: Any] = {\\n        guard let dict = Bundle.main.infoDictionary else {\\n            fatalError(\\\"Plist file not found\\\")\\n        }\\n\\n        return dict\\n    }()\\n    // highlight-end    \\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3ParsingConfigs6\\n// $$liquidcoderFilename-ConfigParser\\n\\nimport Foundation\\n\\npublic enum ConfigParser {\\n\\n enum Keys {\\n    static let baseUrl = \\\"BASE_URL\\\"\\n }\\n\\n    private static let infoDictionary: [String: Any] = {\\n        guard let dict = Bundle.main.infoDictionary else {\\n            fatalError(\\\"Plist file not found\\\")\\n        }\\n\\n        return dict\\n    }()\\n\\n    // highlight-start\\n    static let baseUrl: URL = {\\n      guard let baseUrlString = infoDictionary[Keys.baseUrl] as? String else {\\n          fatalError(\\\"BASE_URL not available in plist file\\\")\\n      }\\n      guard let url = URL(string: baseUrlString) else {\\n          fatalError(\\\"Invalid BASE_URL\\\")\\n      }\\n\\n      return url\\n\\n    }()\\n    // highlight-end    \\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3ApiService1\\n// $$liquidcoderFilename-ApiServiceProtocol\\n\\nimport Foundation\\n\\npublic protocol ApiServiceProtocol {\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\n// $$liquidcoderID-Lesson3ApiService2\\n// $$liquidcoderFilename-ApiServiceProtocol\\n\\nimport Foundation\\n    // highlight-start\\nimport Combine\\n    // highlight-end\\n\\npublic protocol ApiServiceProtocol {\\n\\n    // highlight-start\\n    func remotePublisher(term: String, limit: Int) -> AnyPublisher<Data, URLError>\\n    func localPublisher(filename: String) -> AnyPublisher<Data, URLError>\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3ApiService3\\n// $$liquidcoderFilename-ApiService\\n\\nimport Foundation\\n\\n    // highlight-start\\npublic class ApiService: ApiServiceProtocol {\\n    // highlight-end\\n\\n// highlight-start\\n    func remotePublisher(term: String, limit: Int) -> AnyPublisher<Data, URLError> {\\n\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    func localPublisher(filename: String) -> AnyPublisher<Data, URLError> {\\n\\n    }\\n    // highlight-end\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3ApiService4\\n// $$liquidcoderFilename-ApiService\\n\\nimport Foundation\\n\\npublic class ApiService: ApiServiceProtocol {\\n\\n        // highlight-start\\n    private var session: URLSession {\\n        let config = URLSessionConfiguration.default\\n        config.urlCache = URLCache.shared\\n        config.waitsForConnectivity = true\\n        config.requestCachePolicy = .reloadRevalidatingCacheData\\n        return URLSession(configuration: config, delegate: nil, delegateQueue: nil)\\n    }\\n        // highlight-end\\n\\n        func remotePublisher(term: String, limit: Int) -> AnyPublisher<Data, URLError> {\\n\\n        }\\n\\n    func localPublisher(filename: String) -> AnyPublisher<Data, URLError> {\\n\\n    }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3ApiService5\\n// $$liquidcoderFilename-ApiService\\n\\nimport Foundation\\n\\npublic class ApiService: ApiServiceProtocol {\\n\\n    private var session: URLSession {\\n        let config = URLSessionConfiguration.default\\n        config.urlCache = URLCache.shared\\n        config.waitsForConnectivity = true\\n        config.requestCachePolicy = .reloadRevalidatingCacheData\\n        return URLSession(configuration: config, delegate: nil, delegateQueue: nil)\\n    }\\n\\n        // highlight-start\\n    private func buildUrl(term: String, limit: Int ) -> URL? {\\n        let queryItems = [\\n            URLQueryItem(name: \\\"entity\\\", value: \\\"podcast\\\"),\\n            URLQueryItem(name: \\\"term\\\", value: term),\\n            URLQueryItem(name: \\\"limit\\\", value: \\\"\\\\(limit)\\\")\\n        ]\\n\\n        var components = URLComponents(url: ConfigParser.baseUrl, resolvingAgainstBaseURL: false)\\n        components?.queryItems = queryItems\\n\\n        return components?.url\\n\\n    }\\n        // highlight-end\\n\\n        func remotePublisher(term: String, limit: Int) -> AnyPublisher<Data, URLError> {\\n\\n        }\\n\\n    func localPublisher(filename: String) -> AnyPublisher<Data, URLError> {\\n\\n    }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3ApiService6\\n// $$liquidcoderFilename-ApiService\\n\\nimport Foundation\\n\\npublic class ApiService: ApiServiceProtocol {\\n\\n        private var session: URLSession { ... }\\n\\n    private func buildUrl(term: String, limit: Int ) -> URL? { ... }\\n\\n    public func remotePublisher(term: String, limit: Int) -> AnyPublisher<Data, URLError> {\\n        // highlight-start\\n        guard let url = buildUrl(term: term, limit: limit) else {\\n            return Fail(error: URLError(.badURL)).eraseToAnyPublisher()\\n        }\\n        // highlight-end\\n\\n    }\\n\\n    func localPublisher(filename: String) -> AnyPublisher<Data, URLError> {\\n\\n    }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3ApiService7\\n// $$liquidcoderFilename-ApiService\\n\\nimport Foundation\\n\\npublic class ApiService: ApiServiceProtocol {\\n\\n        private var session: URLSession { ... }\\n\\n    private func buildUrl(term: String, limit: Int ) -> URL? { ... }\\n\\n    public func remotePublisher(term: String, limit: Int) -> AnyPublisher<Data, URLError> {\\n        guard let url = buildUrl(term: term, limit: limit) else {\\n            return Fail(error: URLError(.badURL)).eraseToAnyPublisher()\\n        }\\n\\n        // highlight-start\\n        return session\\n            .dataTaskPublisher(for: url)\\n        // highlight-end\\n    }\\n\\n    func localPublisher(filename: String) -> AnyPublisher<Data, URLError> {\\n\\n        }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3ApiService8\\n// $$liquidcoderFilename-ApiService\\n\\nimport Foundation\\n\\npublic class ApiService: ApiServiceProtocol {\\n\\n        private var session: URLSession { ... }\\n\\n    private func buildUrl(term: String, limit: Int ) -> URL? { ... }\\n\\n    public func remotePublisher(term: String, limit: Int) -> AnyPublisher<Data, URLError> {\\n        guard let url = buildUrl(term: term, limit: limit) else {\\n            return Fail(error: URLError(.badURL)).eraseToAnyPublisher()\\n        }\\n\\n        return session\\n            .dataTaskPublisher(for: url)\\n            // highlight-start\\n            .retry(3)\\n            // highlight-end\\n\\n    }\\n\\n    func localPublisher(filename: String) -> AnyPublisher<Data, URLError> {\\n\\n        }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3ApiService9\\n// $$liquidcoderFilename-ApiService\\n\\nimport Foundation\\n\\npublic class ApiService: ApiServiceProtocol {\\n\\n        private var session: URLSession { ... }\\n\\n    private func buildUrl(term: String, limit: Int ) -> URL? { ... }\\n\\n    public func remotePublisher(term: String, limit: Int) -> AnyPublisher<Data, URLError> {\\n        guard let url = buildUrl(term: term, limit: limit) else {\\n            return Fail(error: URLError(.badURL)).eraseToAnyPublisher()\\n        }\\n\\n        return session\\n            .dataTaskPublisher(for: url)\\n            .retry(3)\\n            // highlight-start\\n            .map(\\\\.data)\\n            // highlight-end\\n\\n    }\\n\\n    func localPublisher(filename: String) -> AnyPublisher<Data, URLError> {\\n\\n        }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3ApiService10\\n// $$liquidcoderFilename-ApiService\\n\\nimport Foundation\\n\\npublic class ApiService: ApiServiceProtocol {\\n\\n        private var session: URLSession { ... }\\n\\n    private func buildUrl(term: String, limit: Int ) -> URL? { ... }\\n\\n    public func remotePublisher(term: String, limit: Int) -> AnyPublisher<Data, URLError> {\\n        guard let url = buildUrl(term: term, limit: limit) else {\\n            return Fail(error: URLError(.badURL)).eraseToAnyPublisher()\\n        }\\n\\n        return session\\n            .dataTaskPublisher(for: url)\\n            .retry(3)\\n            .map(\\\\.data)\\n        // highlight-start\\n            .eraseToAnyPublisher()\\n        // highlight-end\\n    }\\n\\n    func localPublisher(filename: String) -> AnyPublisher<Data, URLError> {\\n\\n        }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3ApiService11\\n// $$liquidcoderFilename-ApiService\\n\\nimport Foundation\\n\\npublic class ApiService: ApiServiceProtocol {\\n\\n        private var session: URLSession { ... }\\n\\n    private func buildUrl(term: String, limit: Int ) -> URL? { ... }\\n\\n    func remotePublisher(term: String, limit: Int) -> AnyPublisher<Data, URLError> {\\n\\n        guard let url = buildUrl(term: term, limit: limit) else {\\n            return Fail(error: URLError(.badURL) ).eraseToAnyPublisher()\\n        }\\n\\n        return session\\n            .dataTaskPublisher(for: url)\\n            .retry(3)\\n            .map(\\\\.data)\\n            .eraseToAnyPublisher()\\n    }\\n\\n    public func localPublisher(filename: String) -> AnyPublisher<Data, URLError> {\\n\\n        // highlight-start\\n        guard let url = Bundle.main.url(forResource: filename, withExtension: \\\"json\\\"),\\n              let data = try? Data(contentsOf: url) else {\\n            return Fail(error: URLError(.badURL) ).eraseToAnyPublisher()\\n        }\\n        // highlight-end\\n    }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson3ApiService12\\n// $$liquidcoderFilename-ApiService\\n\\nimport Foundation\\n\\npublic class ApiService: ApiServiceProtocol {\\n\\n        private var session: URLSession { ... }\\n\\n    private func buildUrl(term: String, limit: Int ) -> URL? { ... }\\n\\n    func remotePublisher(term: String, limit: Int) -> AnyPublisher<Data, URLError> {\\n\\n        guard let url = buildUrl(term: term, limit: limit) else {\\n            return Fail(error: URLError(.badURL) ).eraseToAnyPublisher()\\n        }\\n\\n        return session\\n            .dataTaskPublisher(for: url)\\n            .retry(3)\\n            .map(\\\\.data)\\n            .eraseToAnyPublisher()\\n    }\\n\\n    public func localPublisher(filename: String) -> AnyPublisher<Data, URLError> {\\n\\n        guard let url = Bundle.main.url(forResource: filename, withExtension: \\\"json\\\"),\\n              let data = try? Data(contentsOf: url) else {\\n            return Fail(error: URLError(.badURL) ).eraseToAnyPublisher()\\n        }\\n\\n        // highlight-start\\n        return CurrentValueSubject<Data, URLError>(data).eraseToAnyPublisher()\\n        // highlight-end\\n\\n    }\\n\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson3Introduction-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey guys, this is lesson number 3, if you haven't checked 1 and 2 you can check them before continuing with one, but if you want to start from here, you can still do it providing that you have downloaded the previous lesson's source code.\")), mdx(\"div\", {\n    id: \"Lesson3FixingErrors1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the previous lesson, we left  few errors that I said  we would fix first thing in the next lesson. Here we are now. \"), mdx(\"p\", null, \"As you can see, we are missing  a couple of extensions. Let's add them now.\")), mdx(\"div\", {\n    id: \"Lesson3DateExtensions1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Extensions\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Shared\"), \" folder, add a folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Extensions\")), \", and inside it, add a swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Date+\")), \" containing the following code.\"), mdx(\"p\", null, \"In the above code, we are converting a date to string. \")), mdx(\"div\", {\n    id: \"Lesson3StringExtensions1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the same folder, add another swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"String+\"), \" containing the following code:\"), mdx(\"p\", null, \"Here is what the 2 functions do:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The  first function convert a string date to a format that we want.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The second one convert back a string date to \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Date\"), \" type.\")), mdx(\"p\", null, \"Now build the project (CMD + B), and all of your errors you should away. However, you may get a few warnings from SwiftLint. This is a good time to go over what we have just done.\")), mdx(\"div\", {\n    id: \"Lesson3ModifyingSwiftLintRules1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Here is an example on how to  modifier the swiftLint file. When you built the project, you found out that we are getting warnings due to the shortness of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"id\"), \" property, but we need the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"id\"), \" to be named that way because of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Identifiable\"), \" protocol, so the thing to change here is the rule in swiftLint rather than the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"id\"), \" name. Here is how we do it: \")), mdx(\"div\", {\n    id: \"Lesson3ModifyingSwiftLintRules2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Go to the link I provided in the previous lesson, the one to the rules, and look for \\\"\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Identifier Name\\\".\"), \"  You will find a brief explanation of the rule and what you need to do to change any rule you want.\"), mdx(\"p\", null, \"This link \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://realm.github.io/SwiftLint/rule-directory.html\"\n  }), \"https://realm.github.io/SwiftLint/rule-directory.html\"), \".\")), mdx(\"div\", {\n    id: \"Lesson3ModifyingSwiftLintRules3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the image, we are going to take identifier as key and its value will be one of the value from the default configuration. \")), mdx(\"div\", {\n    id: \"Lesson3ModifyingSwiftLintRules4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"So we will have something like in the displayed code\"), mdx(\"p\", null, \"Add the above code inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \".swiftlint.yml\")), \" file. The indentation is important, make sure the it correct.\")), mdx(\"div\", {\n    id: \"Lesson3ServicesCreationPitch1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"If you have been following my free courses on the website, you might have noticed that I do one thing at a time, and in most cases,  I only move on to the next until I am done the current one, I am talking about services and views of course. However, in this one we will not do that...  If it's a big service, we will build it in chunks while building its corresponding views at the same time.\")), mdx(\"div\", {\n    id: \"Lesson3InternetMonitor1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Monitoring Internet connexion status\"), mdx(\"p\", null, \"Right now, we are about to implement the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiService\"), \" using the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Combine\")), \" framework, but before we do that, let's first implement a service that will notify interested components about the status of the internet connexion. \")), mdx(\"div\", {\n    id: \"Lesson3InternetMonitor2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now create a new folder inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Shared\")), \" one, and name it \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Networking\"), \".\"), \" This is the folder that will contain all our networking related services. Inside that folder, create a swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"InternetMonitor\"), \".\"), \" \")), mdx(\"div\", {\n    id: \"Lesson3InternetMonitor3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We start by importing the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Network\"), \" package as that's the one containing the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NWPathMonitor\"), \" we are using in the class.\")), mdx(\"div\", {\n    id: \"Lesson3InternetMonitor4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We then declare the monitor itself and a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Published\"), \" property that will notify any interested views that will listen to it hence the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"@Published\"), \" property wrapper.\")), mdx(\"div\", {\n    id: \"Lesson3InternetMonitor5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"init\"), \" function, we first create a dispatch queue on which the monitor will perform its observation. Inside the handler closure, we check. the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"path\"), \" \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"status\"), \" and update \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isAvailable\"), \"  based on whether the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"status\"), \" is satisfied or not.\")), mdx(\"div\", {\n    id: \"Lesson3InternetMonitor6-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Last, we start monitoring on the queue created above.\"), mdx(\"p\", null, \"This class will most likely change down the line, but right now this is all we need.  \")), mdx(\"div\", {\n    id: \"Lesson3InternetMonitor7-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"So to test this, replace everything in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ContentView\"), \" with the following: \"), mdx(\"p\", null, \"Now run the app. If you are connected to the internet, you should see \\\"\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Connected!\\\"\"), \" in green. Now switch off your connection and wait  a few seconds, and you should now see \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"\\\"Not connected\\\"\"), \" in red.  Now let's create the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiService\"), \".\")), mdx(\"div\", {\n    id: \"Lesson3ParsingConfigs0-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiService\"), \" , we will need to use the base url when constructing the request url. And remember that we have created the config files in order to store such values inside, so let's add the base url in the configs files now.\")), mdx(\"div\", {\n    id: \"Lesson3ParsingConfigs1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Add the following line in all 3 config files \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"(Product.xcconfig, Development.xcconfig, Staging.xcconfig )\"))), mdx(\"p\", null, \"Now this value is already available to all 3 environments, but we need to find a way of reading it in our code. We will use the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"info.plist\"), \" to propagate the value in our code. \")), mdx(\"div\", {\n    id: \"Lesson3ParsingConfigs2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Add a new entry in all 3 targets \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"info.plist\"), \" files except the watchOS target\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \",\"), \" name the key \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"BASE_URL\"), \" and give it \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"this value $(BASE_URL),\"), \"  you should have this in your info.plist files:\")), mdx(\"div\", {\n    id: \"Lesson3ParsingConfigs3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We have the value in the info.plist files, now it is time to read it. In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Configs\")), \" folder, add a swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"ConfigParser.\"), \" \"), mdx(\"p\", null, \"We create the entire ConfigParser as enum for simplicity. \")), mdx(\"div\", {\n    id: \"Lesson3ParsingConfigs4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next, we create another enum to hold the keys that we've added in the info.plist file.\")), mdx(\"div\", {\n    id: \"Lesson3ParsingConfigs5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"infoDictionary\"), \" property closure, we retrieve the info.plist content and return it. If we encounter any error, we kill the app, the app should not run without the info.plist.\")), mdx(\"div\", {\n    id: \"Lesson3ParsingConfigs6-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Last, we retrieve the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"baseUrl\"), \" value from the dictionary if all checks went well, otherwise we kill the app, without the baseUrl, the app should not run.\")), mdx(\"div\", {\n    id: \"Lesson3ApiService1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Api Service\"), mdx(\"p\", null, \"We want to make the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiService\"), \" class injectable, and to achieve that we must first create a protocol to which it will conform. So in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Networking\"), \" folder, add a swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"ApiServiceProtocol.\"))), mdx(\"div\", {\n    id: \"Lesson3ApiService2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We first import \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Combine\"), \", then we create 2 functions that return  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AnyPublisher\"), \" . The first one will be used for remote calls and the second one will  be used to parse local files.\")), mdx(\"div\", {\n    id: \"Lesson3ApiService3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the same folder, add another swift file , and name it *ApiService\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"*.\")), mdx(\"p\", null, \"Like I said,  the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiService\"), \" class needs to conform to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiServiceProtocol\"), \" , hence the 2 required methods, we will fill them shortly, don't mind the errors.\")), mdx(\"div\", {\n    id: \"Lesson3ApiService4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"session\"), \" is  the object we will use to make http request to fetch data over the network. As you can see, we are  using the default configuration, but there are 2 other configurations (\", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ephemeral\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"background\"), \") that we don't need here. We also set the cache to the shared one, if you have extra requirements, you can instantiate a new \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"URLCache\"), \" object. The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"waitsForConnectivity\"), \" is set to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"true\"), \" to do exactly that , it will pause  any processing that was being done until the internet connection is available. The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"requestCachePolicy\"), \" set there is saying that we can load cached data as long as it's validated by the source, otherwise we load from the source url.\")), mdx(\"div\", {\n    id: \"Lesson3ApiService5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"buildUrl\"), \" function construct a valid url using the term and limit that were passed in. For example, if we pass in \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"technology\"), \" as term and \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"20\"), \" as limit, this will produce the following url \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://itunes.apple.com/search?entity=podcast&term=technology&limit=20\"\n  }), \"https://itunes.apple.com/search?entity=podcast&term=technology&limit=20\"), \" using the baseUrl we've constructed in the previous section. The items after the question mark are called query parameters.\")), mdx(\"div\", {\n    id: \"Lesson3ApiService6-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"remotePublisher\"), \" , we check if the url is not nil, if it is , we return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Fail\"), \" Publisher passing in the error. The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Fail\"), \" struct will create a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"publisher\"), \" that fails immediately. \")), mdx(\"div\", {\n    id: \"Lesson3ApiService7-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the case where the url is valid, we proceed to creating the success publisher.\"), mdx(\"p\", null, \"The session has a method  that returns a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Publisher\"), \", we call it, passing in the url built above.\")), mdx(\"div\", {\n    id: \"Lesson3ApiService8-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"retry\"), \" operator will retry the execution 3 times in case of a failure before aborting altogether.\")), mdx(\"div\", {\n    id: \"Lesson3ApiService9-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We then use the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"map\"), \" operator to retrieve the data portion from the tuple result \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"(data: Data, response: URLResponse)\"), \" returned by the the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"retry\"), \" operator.\")), mdx(\"div\", {\n    id: \"Lesson3ApiService10-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"map\"), \" operator will return a monster of type that will be simplified by calling \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"eraseToAnyPublisher\"), \" on the return of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"map\"), \". \")), mdx(\"div\", {\n    id: \"Lesson3ApiService11-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Last, In \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"localPublisher\"), \"  function , we read the content of a file given its name. We create  a full url using \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Bundle\"), \" , then read its data. In case of a problem, we return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Fail\"), \" publisher.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Download the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, mdx(\"strong\", {\n    parentName: \"em\"\n  }, \"genres.json\")), \" file with this \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://u.pcloud.link/publink/show?code=XZV5shXZBklcRMideNb4cMCKyXli2Rs84MQ7\",\n    \"title\": \"Genre.json\"\n  }), \"link\"), \" or you can download \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://u.pcloud.link/publink/show?code=XZbzemXZfFYlwAnBTRyBmbTnSCrrKmGdMtoV\",\n    \"title\": \"Stream cast lesson 3\"\n  }), \"the finished lesson #3\"), \", and get it from there.\"))), mdx(\"div\", {\n    id: \"Lesson3ApiService12-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"If everything went well with building the url, we then return a constant \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Publisher\"), \" that we also erase to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"anyPublisher\"), \" to simplify its return type\"), mdx(\"p\", null, \"This is the end of lesson 3, let's move on to lessson 4.\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#extensions","title":"Extensions"},{"url":"#monitoring-internet-connexion-status","title":"Monitoring Internet connexion status"},{"url":"#api-service","title":"Api Service"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Hey guys, this is lesson number 3, if you haven't checked 1 and 2 you can check them before continuing with one, but if you want to start from here, you can still do it providing that you have downloaded the previous lesson's source code.\"), mdx(\"h2\", null, \"Fixing the errors\"), mdx(\"p\", null, \"In the previous lesson, we left  few errors that I said  we would fix first thing in the next lesson. Here we are now. \"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610203887/StreamCast/mlbtw8tn4g9ljcx24cf9.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610203887/StreamCast/mlbtw8tn4g9ljcx24cf9.png\"\n  }))), mdx(\"p\", null, \"As you can see, we are missing  a couple of extensions. Let's add them now.\"), mdx(\"h3\", null, \"Extensions\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Shared\"), \" folder, add a folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Extensions\")), \", and inside it, add a swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Date+\")), \" containing the following code:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Date+.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"extension Date {\\n    func format(_ format: String = \\\"d MMM yyyy\\\") -> String {\\n           let formatter = DateFormatter()\\n           formatter.dateFormat = format\\n            formatter.locale = Locale.autoupdatingCurrent\\n           let component = formatter.string(from: self)\\n           return component\\n    }\\n}\\n\")), mdx(\"p\", null, \"In the above code, we are converting a date to string. In the same folder, add another swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"String+\"), \" containing the following code:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"String+.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\n\\nextension String {\\n\\n    private func createFormatter() -> DateFormatter {\\n\\n        let formatter = DateFormatter()\\n        formatter.locale =  .autoupdatingCurrent\\n        formatter.dateFormat = \\\"yyyy-MM-dd'T'HH:mm:ssZ\\\"\\n\\n        return formatter\\n    }\\n\\n    func formatDate() -> String {\\n        let formatter = createFormatter()\\n        if let date = formatter.date(from: self) {\\n            return date.format()\\n        }\\n\\n        return \\\"\\\"\\n    }\\n\\n    func toDate() -> Date {\\n        let formatter = createFormatter()\\n        if let date = formatter.date(from: self) {\\n            return date\\n        }\\n\\n        return Date()\\n    }\\n\\n}\\n\")), mdx(\"p\", null, \"Here is what the 2 functions do:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The  first function convert a string date to a format that we want.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The second one convert back a string date to \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Date\"), \" type.\")), mdx(\"p\", null, \"Now build the project (CMD + B), and all of your errors you should away. However, you may get a few warnings from SwiftLint. This is a good time to go over what we have just done.\"), mdx(\"p\", null, \"Here is an example on how to  modifier the swiftLint file. When you built the project, you found out that we are getting warnings due to the shortness of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"id\"), \" property, but we need the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"id\"), \" to be named that way because of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Identifiable\"), \" protocol, so the thing to change here is the rule in swiftLint rather than the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"id\"), \" name. Here is how we do it: \"), mdx(\"p\", null, \"Go to the link I provided in the previous lesson, the one to the rules, and look for \\\"\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Identifier Name\\\".\"), \"  You will find a brief explanation of the rule and what you need to do to change any rule you want.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610204789/StreamCast/jtavjuxbaiccs0xhbwmt.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610204789/StreamCast/jtavjuxbaiccs0xhbwmt.png\"\n  }))), mdx(\"p\", null, \"In the above image, we are going to take identifier as key and its value will be one of the value from the default configuration. So we will have something like this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"identifier_name:\\n    min_length: 2\\n\")), mdx(\"p\", null, \"Add the above code inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \".swiftlint.yml\")), \" file. The indentation is important, make sure the it correct.\"), mdx(\"p\", null, \"If you have been following my free courses on the website, you might have noticed that I do one thing at a time, and in most cases,  I only move on to the next until I am done the current one, I am talking about services and views of course. However, in this one we will not do that...  If it's a big service, we will build it in chunks while building its corresponding views at the same time.\"), mdx(\"h2\", null, \"Monitoring Internet connexion status\"), mdx(\"p\", null, \"Right now, we are about to implement the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiService\"), \" using the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Combine\")), \" framework, but before we do that, let's first implement a service that will notify interested components about the status of the internet connexion. \"), mdx(\"p\", null, \"Now create a new folder inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Shared\")), \" one, and name it \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Networking\"), \".\"), \" This is the folder that will contain all our networking related services. Inside that folder, create a swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"InternetMonitor\"), \".\"), \" \"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"InternetMonitor.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\nimport Network\\n\\nclass InternetMonitor: ObservableObject {\\n\\n    private let monitor = NWPathMonitor()\\n\\n    @Published var isAvailable = true\\n\\n    init() {\\n\\n        let queue = DispatchQueue(label: \\\"com.liquidcoder.StreamCast.InternetMonitor\\\")\\n        monitor.pathUpdateHandler = { path in\\n            DispatchQueue.main.async {\\n\\n              self.isAvailable = path.status == .satisfied\\n            }\\n        }\\n\\n        monitor.start(queue: queue)\\n    }\\n}\\n\")), mdx(\"p\", null, \"This class will most likely change down the line, but right now all we need is that.  Here is what we have done here.\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We start by importing the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Network\"), \" package as that's the one containing the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"NWPathMonitor\"), \" we are using in the class.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then declare the monitor itself and a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Published\"), \" property that will notify any interested views that will listen to it hence the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"@Published\"), \" property wrapper.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the init function, we first create a dispatch queue on which the monitor will perform its observation. Inside the handler closure, we check. the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"path\"), \" \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"status\"), \" and update \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"isAvailable\"), \"  based on whether the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"status\"), \" is satisfied or not.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Last we start monitoring on the queue created above.\")), mdx(\"p\", null, \"So to test this, replace everything in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ContentView\"), \" with the following: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct ContentView: View {\\n\\n    @StateObject private var internetMonitor = InternetMonitor()\\n\\n    var body: some View {\\n            Text( internetMonitor.isAvailable ? \\\"Connected!\\\" : \\\"Not connected\\\")\\n                .bold()\\n                .foregroundColor( internetMonitor.isAvailable ? .green : .red)\\n\\n    }\\n}\\n\\nstruct ContentView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        ContentView()\\n    }\\n}\\n\")), mdx(\"p\", null, \"Now run the app. If you are connected to the internet, you should see \\\"\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Connected!\\\"\"), \" in green. Now switch off your connection and wait  a few seconds, and you should now see \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"\\\"Not connected\\\"\"), \" in red.  Now let's create the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiService\"), \".\"), mdx(\"h2\", null, \"Configs\"), mdx(\"h3\", null, \"Adding value in info.plist\"), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiService\"), \" , we will need to use the base url when constructing the request url. And remember that we have created the config files in order to store such values inside, so let's add the base url in the configs files now.\"), mdx(\"p\", null, \"Add the following line in all 3 config files (Product.xcconfig, Development.xcconfig, Staging.xcconfig ):\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-bash\"\n  }), \"BASE_URL = https:/$()/itunes.apple.com/search\\n\")), mdx(\"p\", null, \"Now this value is already available to all 3 environments, but we need to find a way of reading it in our code. We will use the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"info.plist\"), \" to propagate the value in our code. \"), mdx(\"p\", null, \"Add a new entry in all 3 targets \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"info.plist\"), \" files except the watchOS target\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \",\"), \" name the key \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"BASE_URL\"), \" and give it \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"this value $(BASE_URL),\"), \"  you should have this in your info.plist files:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610205550/StreamCast/dzjf6h9mnd1pmdgroic5.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1610205550/StreamCast/dzjf6h9mnd1pmdgroic5.png\"\n  }))), mdx(\"h3\", null, \"Parsing the info.plist\"), mdx(\"p\", null, \"We have the value in the info.plist files, now it is time to read it. In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Configs\")), \" folder, add a swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"ConfigParser.\"), \" \"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ConfigParser.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\n\\npublic enum ConfigParser {\\n    enum Keys {\\n        static let baseUrl = \\\"BASE_URL\\\"\\n    }\\n\\n    private static let infoDictionary: [String: Any] = {\\n        guard let dict = Bundle.main.infoDictionary else {\\n            fatalError(\\\"Plist file not found\\\")\\n        }\\n\\n        return dict\\n    }()\\n\\n    static let baseUrl: URL = {\\n        guard let baseUrlString = infoDictionary[Keys.baseUrl] as? String else {\\n            fatalError(\\\"BASE_URL not available in plist file\\\")\\n        }\\n        guard let url = URL(string: baseUrlString) else {\\n            fatalError(\\\"Invalid BASE_URL\\\")\\n        }\\n\\n        return url\\n\\n    }()\\n\\n}\\n\")), mdx(\"p\", null, \"Here is what we've done here:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We create the entire ConfigParser as enum for simplicity. \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Next, we create another enum to hold the keys that we've added in the info.plist file.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"infoDictionary\"), \" property closure, we retrieve the info.plist content and return it. If we encounter any error, we kill the app, the app should not run without the info.plist.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Last, we retrieve the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"baseUrl\"), \" value from the dictionary if all checks went well, otherwise we kill the app, without the baseUrl, the app should not run.\")), mdx(\"h2\", null, \"Api Service\"), mdx(\"p\", null, \"We want to make the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiService\"), \" class injectable, and to achieve that we must first create a protocol to which it will conform. So in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Networking\"), \" folder, add a swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"ApiServiceProtocol.\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ApiServiceProtocol.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\nimport Combine\\n\\npublic protocol ApiServiceProtocol: class {\\n    func remotePublisher(term: String, limit: Int) -> AnyPublisher<Data, URLError>\\n    func localPublisher(filename: String) -> AnyPublisher<Data, URLError>\\n}\\n\")), mdx(\"p\", null, \"Here is a quick rundown of the code:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We first import \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Combine\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We create 2 functions that return  \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"AnyPublisher\"), \" .\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The first one will be used for remote calls.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"And the second one will  be used to parse local files.\")), mdx(\"p\", null, \"In the same folder, add another swift file , and name it *ApiService\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"*.\")), mdx(\"p\", null, \"ApiService.swift\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\nimport Combine\\n\\npublic class  ApiService: ApiServiceProtocol {\\n\\n    private var session: URLSession {\\n        let config = URLSessionConfiguration.default\\n        config.urlCache = URLCache.shared\\n        config.waitsForConnectivity = true\\n        config.requestCachePolicy = .reloadRevalidatingCacheData\\n        return URLSession(configuration: config, delegate: nil, delegateQueue: nil)\\n    }\\n\\n    private func buildUrl(term: String, limit: Int ) -> URL? {\\n        let queryItems = [\\n            URLQueryItem(name: \\\"entity\\\", value: \\\"podcast\\\"),\\n            URLQueryItem(name: \\\"term\\\", value: term),\\n            URLQueryItem(name: \\\"limit\\\", value: \\\"\\\\(limit)\\\")\\n        ]\\n\\n        var components = URLComponents(url: ConfigParser.baseUrl, resolvingAgainstBaseURL: false)\\n        components?.queryItems = queryItems\\n\\n        return components?.url\\n\\n    }\\n\\n    public func remotePublisher(term: String, limit: Int) -> AnyPublisher<Data, URLError> {\\n        guard let url = buildUrl(term: term, limit: limit) else {\\n            return Fail(error: URLError(.badURL)).eraseToAnyPublisher()\\n        }\\n\\n        return session\\n            .dataTaskPublisher(for: url)\\n            .retry(3)\\n            .map(\\\\.data)\\n            .eraseToAnyPublisher()\\n    }\\n\\n    public func localPublisher(filename: String) -> AnyPublisher<Data, URLError> {\\n\\n        guard let url = Bundle.main.url(forResource: filename, withExtension: \\\"json\\\"),\\n              let data = try? Data(contentsOf: url) else {\\n            return Fail(error: URLError(.badURL) ).eraseToAnyPublisher()\\n        }\\n\\n        return CurrentValueSubject<Data, URLError>(data).eraseToAnyPublisher()\\n\\n    }\\n}\\n\")), mdx(\"p\", null, \"Here is what we are doing here:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"session\"), \" is  the object we will use to make http request to fetch data over the network. As you can see, we are  using the default configuration, but there are 2 other configurations (\", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ephemeral\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"background\"), \") that we don't need here. We also set the cache to the shared one, if you have extra requirements, you can instantiate a new \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"URLCache\"), \" object. The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"waitsForConnectivity\"), \" is set to \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"true\"), \" to do exactly that , it will pause  any processing that was being done until the internet connection is available. The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"requestCachePolicy\"), \" set there is saying that we can load cached data as long as it's validated by the source, otherwise we load from the source url.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"buildUrl\"), \" function construct a valid url using the term and limit that were passed in. For example, if we pass in \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"technology\"), \" as term and \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"20\"), \" as limit, this will produce the following url \", mdx(\"a\", _extends({\n    parentName: \"li\"\n  }, {\n    \"href\": \"https://itunes.apple.com/search?entity=podcast&term=technology&limit=1\"\n  }), \"https://itunes.apple.com/search?entity=podcast&term=technology&limit=1\"), \" using the baseUrl we've constructed in the previous section. The items after the question mark are called query parameters.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"remotePublisher\"), \"  checks if the url is not nil, if it is , we return a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Fail\"), \" Publisher passing in the error. The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Fail\"), \" struct will create a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"publisher\"), \" that fails immediately. In the case where the url is valid, we proceed to creating the success publisher. The session has a method  that returns a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Publisher\"), \" which we then use the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"map\"), \" operator to retrieve the data portion from the tuple result \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"(data: Data, response: URLResponse)\"), \" .  The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"map\"), \" operator will return a monster of type that will be simplified by calling \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"eraseToAnyPublisher\"), \" on the return of the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"map\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Last, In \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"localPublisher\"), \"  function , we read the content of a file given its name. We create  a full url using \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Bundle\"), \" , then read its data and we return a constant \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Publisher\"), \" that we also  erase to \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"anyPublisher\"), \" to simplify its return type. In case of a problem, we return a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Fail\"), \" publisher.\")), mdx(\"p\", null, \"This is it for lesson #3 guys, let's move on to #4. Happy coding!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#fixing-the-errors","title":"Fixing the errors"},{"url":"#monitoring-internet-connexion-status","title":"Monitoring Internet connexion status"},{"url":"#configs","title":"Configs"},{"url":"#api-service","title":"Api Service"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-01-21T16:51:53.448Z","isNewLayout":true,"previews":{"previews":"{\n    \"Lesson4ContentViewUpdate5\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1612454856/StreamCast/fnscovwab9azmabigprw.png\" ]\n}"},"slug":"lesson-4-store","title":"Lesson #4: Store","updatedDate":"2021-01-21T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZ13emXZJSaafDVORI8swAJbsOrhTSDvAmvk","type":"zip","id":"32f14b52-81d7-5114-bbb3-89dddbeec2f1","name":"Source code"}],"allChapters":["Lesson4Introduction: Introduction","Lesson4LoggingUtils: Logging utils","Lesson4Redux1: SwiftUI - Redux","Lesson4Action1: Action","Lesson4AppEnv1: App Environment","Lesson4ApiReducer1: Api Reducer","Lesson4Store1: Store","Lesson4ContentViewUpdate1: Lesson4ContentViewUpdate1"],"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4LoggingUtils\\n// $$liquidcoderFilename-Log\\n\\nimport Foundation\\nimport SwiftUI\\n\\nextension Date {\\n    func toString() -> String {\\n        return Log.dateFormatter.string(from: self as Date)\\n    }\\n}\\n\\nenum LogEvent: String {\\n    case error = \\\"\\uD83D\\uDCA5\\uD83D\\uDCA5\\uD83D\\uDCA5\\\"\\n    case info = \\\"\\u2139\\uFE0F\\u2139\\uFE0F\\u2139\\uFE0F\\\"\\n    case debug = \\\"\\uD83D\\uDEE0\\uD83D\\uDEE0\\uD83D\\uDEE0\\\"\\n    case verbose = \\\"\\uD83D\\uDCE3\\uD83D\\uDCE3\\uD83D\\uDCE3\\\"\\n    case warning = \\\"\\u26A0\\uFE0F\\u26A0\\uFE0F\\u26A0\\uFE0F\\\"\\n    case severe = \\\"\\uD83D\\uDD25\\uD83D\\uDD25\\uD83D\\uDD25\\\"\\n}\\n\\nstruct Log {\\n\\n    static private var dateFormat = \\\"yyyy-MM-dd hh:mm\\\"\\n    static var dateFormatter: DateFormatter {\\n        let formatter = DateFormatter()\\n        formatter.dateFormat = dateFormat\\n        formatter.locale = Locale.current\\n        formatter.timeZone = TimeZone.current\\n        return formatter\\n    }\\n\\n    private init() {}\\n\\n    static func message(forEvent event: LogEvent, _ object: Any...,\\n                       filename: String = #file,\\n                       line: Int = #line,\\n                       funcName: String = #function) {\\n        #if DEBUG\\n        var objects = \\\"\\\"\\n        object.forEach({objects += \\\"\\\\($0) \\\"})\\n\\n        print(\\\"\\\\(Date().toString()) \\\\(event.rawValue)[\\\\(sourceFileName(filePath: filename))]: \\\\(line) \\\\(funcName) -> \\\\(objects)\\\")\\n        #endif\\n    }\\n\\n        private static func sourceFileName(filePath: String) -> String {\\n        let components = filePath.components(separatedBy: \\\"/\\\")\\n        return components.isEmpty ? \\\"\\\" : components.last!\\n    }\\n\\n    static func error(_ object: Any...) {\\n\\n        message(forEvent: .error, object)\\n    }\\n\\n    static func info(_ object: Any...) {\\n\\n        message(forEvent: .info, object)\\n\\n    }\\n\\n    static func warning(_ object: Any...) {\\n\\n        message(forEvent: .warning, object)\\n\\n    }\\n\\n    static func debug(_ object: Any...) {\\n\\n        message(forEvent: .debug, object)\\n    }\\n\\n    static func verbose(_ object: Any...) {\\n\\n        message(forEvent: .verbose, object)\\n\\n    }\\n\\n    static func severe(_ object: Any...) {\\n\\n        message(forEvent: .severe, object)\\n    }\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4Action1\\n// $$liquidcoderFilename-Action\\n\\nimport Foundation\\n\\n// highlight-start\\nenum Action {\\n\\n}\\n// highlight-end\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4Action2\\n// $$liquidcoderFilename-Action\\n\\nimport Foundation\\n\\nenum Action {\\n\\n    // highlight-start\\n    enum Api {\\n        case fetchPodcasts(_ term: String, limit: Int = 10)\\n        case fetchGenres(_ filename: String)\\n    }\\n    // highlight-end\\n\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4Action3\\n// $$liquidcoderFilename-Action\\n\\nimport Foundation\\n\\nenum Action {\\n\\n    enum Api {\\n        case fetchPodcasts(_ term: String, limit: Int = 10)\\n        case fetchGenres(_ filename: String)\\n    }\\n\\n    // highlight-start\\n    case api(_ action: Api)\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4AppState1\\n// $$liquidcoderFilename-AppState\\n\\nimport Foundation\\n\\n// highlight-start\\nenum AppState {\\n\\n}\\n// highlight-end\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4AppState2\\n// $$liquidcoderFilename-AppState\\n\\nimport Foundation\\nimport SwiftUI\\n\\nenum AppState {\\n\\n// highlight-start\\n    enum Result<T: Equatable>: Equatable {\\n        static func == (lhs: AppState.Result<T>, rhs: AppState.Result<T>) -> Bool {\\n\\n            switch lhs {\\n            case .loading:\\n                if case loading = rhs {\\n                    return true\\n                }\\n            case .success(let lhsValue):\\n                if case success(let rhsValue) = rhs {\\n                    return rhsValue == lhsValue\\n                }\\n\\n            case .failure:\\n                if case failure = rhs {\\n                    return true\\n                }\\n            }\\n\\n            return false\\n        }\\n\\n        case loading\\n        case success(T)\\n        case failure\\n    }\\n// highlight-end\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4AppState3\\n// $$liquidcoderFilename-AppState\\n\\nimport Foundation\\nimport SwiftUI\\n\\nenum AppState {\\n\\n    enum Result<T: Equatable>: Equatable {\\n        static func == (lhs: AppState.Result<T>, rhs: AppState.Result<T>) -> Bool {\\n\\n            switch lhs {\\n            case .loading:\\n                if case loading = rhs {\\n                    return true\\n                }\\n            case .success(let lhsValue):\\n                if case success(let rhsValue) = rhs {\\n                    return rhsValue == lhsValue\\n                }\\n\\n            case .failure:\\n                if case failure = rhs {\\n                    return true\\n                }\\n            }\\n\\n            return false\\n        }\\n\\n        case loading\\n        case success(T)\\n        case failure\\n    }\\n\\n    // highlight-start\\n    struct Api {\\n        var preferredPodcasts: [String: Result<[PodcastViewModel]>] = [:]\\n        var allGenres: Result<[GenreViewModel]> = .loading\\n    }\\n    // highlight-end\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4AppEnv1\\n// $$liquidcoderFilename-AppEnvironment\\n\\nimport Foundation\\n\\n// highlight-start\\nenum AppEnvironment {\\n\\n}\\n// highlight-end\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4AppEnv2\\n// $$liquidcoderFilename-AppEnvironment\\n\\nimport Foundation\\n\\nstruct AppEnvironment {\\n\\n    // highlight-start\\n    let decoder: JSONDecoder\\n    let api: ApiServiceProtocol\\n    // highlight-end\\n\\n    // highlight-start\\n    init(decoder: JSONDecoder = .init(), api: ApiServiceProtocol = ApiService()) {\\n        self.api = api\\n        self.decoder = decoder\\n    }\\n    // highlight-end\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4ApiReducer1\\n// $$liquidcoderFilename-ApiReducer\\nimport Foundation\\n\\n// highlight-start\\nenum ApiReducer {\\n\\n}\\n// highlight-end\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4ApiReducer2\\n// $$liquidcoderFilename-ApiReducer\\nimport Foundation\\n// highlight-start\\nimport Combine\\n// highlight-end\\n\\nenum ApiReducer {\\n\\n    // highlight-start\\n    typealias StateCompletion = ((AppState.Api) -> Void)\\n    // highlight-end\\n\\n    // highlight-start\\n    static func execute(action: Action.Api, state: AppState.Api, environment: AppEnvironment, completion:  @escaping StateCompletion ) -> AnyCancellable {\\n    // highlight-end\\n\\n    }\\n\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4ApiReducer3\\n// $$liquidcoderFilename-ApiReducer\\nimport Foundation\\nimport Combine\\n\\nenum ApiReducer {\\n\\n    typealias StateCompletion = ((AppState.Api\\n    ) -> Void)\\n\\n    static func execute(action: Action.Api, state: AppState.Api, environment: AppEnvironment, completion:  @escaping StateCompletion ) -> AnyCancellable {\\n\\n    switch action {\\n    case .fetchPodcasts(let term, let limit):\\n            // highlight-start\\n        let publisher = environment.api.remotePublisher(term: term, limit: limit)\\n        // highlight-end\\n\\n    case .fetchGenres(let filename):\\n                // highlight-start\\n        let publisher = environment.api.localPublisher(filename: filename)\\n        // highlight-end  \\n    }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4ApiReducer4\\n// $$liquidcoderFilename-ApiReducer\\nimport Foundation\\nimport Combine\\n\\nenum ApiReducer {\\n\\n    typealias StateCompletion = ((AppState.Api\\n    ) -> Void)\\n\\n    static func execute(action: Action.Api, state: AppState.Api, environment: AppEnvironment, completion:  @escaping StateCompletion ) -> AnyCancellable {\\n\\n    switch action {\\n    case .fetchPodcasts(let term, let limit):\\n\\n        let publisher = environment.api.remotePublisher(term: term, limit: limit)\\n            // highlight-start\\n            return subscribe(to: publisher, type: PodcastList.self, environment: environment) { result, _ in\\n                      setPreferredPodcasts(state: state, term: term, result: result, completion: completion)\\n                   }\\n            // highlight-end\\n\\n    case .fetchGenres(let filename):\\n\\n        let publisher = environment.api.localPublisher(filename: filename)\\n\\n            // highlight-start\\n                    return subscribe(to: publisher, type: GenreList.self, environment: environment) { result, _ in\\n                        setAllGenres(state: state, result: result, completion: completion)\\n                    }\\n            // highlight-end\\n    }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4ApiReducer5\\n// $$liquidcoderFilename-ApiReducer\\nimport Foundation\\nimport Combine\\n\\nenum ApiReducer {\\n\\n    typealias StateCompletion = ((AppState.Api\\n    ) -> Void)\\n\\n    static func execute(action: Action.Api, state: AppState.Api, environment: AppEnvironment, completion:  @escaping StateCompletion ) -> AnyCancellable {\\n              ...                  \\n  }\\n\\n    private static func subscribe<T: Codable>(to publisher: AnyPublisher<Data, URLError>, type: T.Type, environment: AppEnvironment, completion: @escaping ( T?, Error?) -> Void) -> AnyCancellable {\\n\\n    }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4ApiReducer6\\n// $$liquidcoderFilename-ApiReducer\\nimport Foundation\\nimport Combine\\n\\nenum ApiReducer {\\n\\n    typealias StateCompletion = ((AppState.Api\\n    ) -> Void)\\n\\n    static func execute(action: Action.Api, state: AppState.Api, environment: AppEnvironment, completion:  @escaping StateCompletion ) -> AnyCancellable {\\n              ...                  \\n  }\\n\\n    private static func subscribe<T: Codable>(to publisher: AnyPublisher<Data, URLError>, type: T.Type, environment: AppEnvironment, completion: @escaping ( T?, Error?) -> Void) -> AnyCancellable {\\n\\n        // highlight-start  \\n        publisher\\n        .decode(type: type, decoder: environment.decoder)\\n        // highlight-end\\n\\n    }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4ApiReducer7\\n// $$liquidcoderFilename-ApiReducer\\nimport Foundation\\nimport Combine\\n\\nenum ApiReducer {\\n\\n    typealias StateCompletion = ((AppState.Api\\n    ) -> Void)\\n\\n    static func execute(action: Action.Api, state: AppState.Api, environment: AppEnvironment, completion:  @escaping StateCompletion ) -> AnyCancellable {\\n              ...                  \\n  }\\n\\n    private static func subscribe<T: Codable>(to publisher: AnyPublisher<Data, URLError>, type: T.Type, environment: AppEnvironment, completion: @escaping ( T?, Error?) -> Void) -> AnyCancellable {\\n\\n        publisher\\n        .decode(type: type, decoder: environment.decoder)\\n// highlight-start          \\n            .receive(on: DispatchQueue.main)\\n// highlight-end\\n\\n    }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4ApiReducer8\\n// $$liquidcoderFilename-ApiReducer\\nimport Foundation\\nimport Combine\\n\\nenum ApiReducer {\\n\\n    typealias StateCompletion = ((AppState.Api\\n    ) -> Void)\\n\\n    static func execute(action: Action.Api, state: AppState.Api, environment: AppEnvironment, completion:  @escaping StateCompletion ) -> AnyCancellable {\\n              ...                  \\n  }\\n\\n    private static func subscribe<T: Codable>(to publisher: AnyPublisher<Data, URLError>, type: T.Type, environment: AppEnvironment, completion: @escaping ( T?, Error?) -> Void) -> AnyCancellable {\\n\\n        publisher\\n        .decode(type: type, decoder: environment.decoder)\\n            .receive(on: DispatchQueue.main)\\n// highlight-start          \\n                .sink { status in\\n                            if case .failure(let error) = status {\\n                                Log.error(error)\\n                                completion(nil, error)\\n                            }\\n                        } receiveValue: { result in\\n                            completion(result, nil)\\n                        }\\n// highlight-end\\n\\n    }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4ApiReducer9\\n// $$liquidcoderFilename-ApiReducer\\nimport Foundation\\nimport Combine\\n\\nenum ApiReducer {\\n\\n    typealias StateCompletion = ((AppState.Api\\n    ) -> Void)\\n\\n    static func execute(action: Action.Api, state: AppState.Api, environment: AppEnvironment, completion:  @escaping StateCompletion ) -> AnyCancellable {\\n              ...                  \\n  }\\n\\n    private static func subscribe<T: Codable>(to publisher: AnyPublisher<Data, URLError>, type: T.Type, environment: AppEnvironment, completion: @escaping ( T?, Error?) -> Void) -> AnyCancellable {\\n            ...  \\n    }\\n\\n    // highlight-start\\n    private static func setAllGenres(state: AppState.Api, result: GenreList?, completion:  @escaping StateCompletion) {\\n        var copyState = state\\n        if let genreList = result {\\n            copyState.allGenres = .success(genreList.genres.map { GenreViewModel($0) })\\n        } else {\\n            copyState.allGenres = .failure\\n        }\\n\\n        completion(copyState)\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    private static func setPreferredPodcasts( state: AppState.Api, term: String, result: PodcastList?, completion:  @escaping StateCompletion) {\\n        var copyState = state\\n\\n      if let podcastList = result {\\n          copyState.preferredPodcasts[term] = .success(podcastList.results\\n                                                      .map { PodcastViewModel(podcast: $0) }\\n                                                      .filter { !$0.feedUrl.isEmpty && $0.totalEpisodes > 0 })\\n      } else {\\n          copyState.preferredPodcasts[term] = .failure\\n      }\\n\\n        completion(copyState)\\n    }\\n// highlight-end\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4Store1\\n// $$liquidcoderFilename-Store\\n\\nimport Foundation\\n// highlight-start\\nimport Combine\\n// highlight-end\\n\\n// highlight-start\\nclass Store: ObservableObject  {\\n\\n}\\n// highlight-end\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4Store2\\n// $$liquidcoderFilename-Store\\n\\nimport Foundation\\nimport Combine\\n\\nclass Store: ObservableObject  {\\n\\n    // highlight-start\\n    @Published private(set) var apiState = AppState.Api()\\n    // highlight-end\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4Store3\\n// $$liquidcoderFilename-Store\\n\\nimport Foundation\\nimport Combine\\n\\nclass Store: ObservableObject  {\\n\\n    @Published private(set) var apiState = AppState.Api()\\n\\n    // highlight-start\\n    private var subscriptions = Set<AnyCancellable>()\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4Store4\\n// $$liquidcoderFilename-Store\\n\\nimport Foundation\\nimport Combine\\n\\nclass Store: ObservableObject {\\n\\n    @Published private(set) var apiState = AppState.Api()\\n\\n    private var subscriptions = Set<AnyCancellable>()\\n\\n    // highlight-start\\n    private let environment: AppEnvironment\\n    // highlight-end\\n\\n    // highlight-start\\n    init(environment: AppEnvironment = AppEnvironment() ) {\\n        self.environment = environment\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    func dispatch(_ action: Action) {\\n        switch action {\\n        case .api(let action):\\n            ApiReducer.execute(action: action, state: apiState, environment: environment) { [weak self] state in\\n                self?.apiState = state\\n            }.store(in: &subscriptions)\\n        }\\n    }\\n    // highlight-end\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4ContentViewUpdate1\\n// $$liquidcoderFilename-ContentView\\n\\nimport SwiftUI\\n\\nstruct ContentView: View {\\n\\n    var body: some View {\\n\\n    }\\n}\\n\\nstruct ContentView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        ContentView()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4ContentViewUpdate2\\n// $$liquidcoderFilename-ContentView\\n\\nimport SwiftUI\\n\\nstruct ContentView: View {\\n\\n    // highlight-start\\n    @StateObject private var store = Store()\\n\\n    private let term = \\\"technology\\\"\\n    // highlight-end   \\n\\n    var body: some View {\\n\\n    }\\n}\\n\\nstruct ContentView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        ContentView()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4ContentViewUpdate3\\n// $$liquidcoderFilename-ContentView\\n\\nimport SwiftUI\\n\\nstruct ContentView: View {\\n\\n    @StateObject private var store = Store()\\n\\n    private let term = \\\"technology\\\"\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        List {\\n            switch store.apiState.preferredPodcasts[term] {\\n            case .loading:\\n\\n            case .success(let data):\\n            case .failure:\\n            case .none:\\n            }\\n        }\\n        // highlight-end\\n    }\\n}\\n\\nstruct ContentView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        ContentView()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4ContentViewUpdate4\\n// $$liquidcoderFilename-ContentView\\n\\nimport SwiftUI\\n\\nstruct ContentView: View {\\n\\n    @StateObject private var store = Store()\\n\\n    private let term = \\\"technology\\\"\\n\\n    var body: some View {\\n        List {\\n            switch store.apiState.preferredPodcasts[term] {\\n            case .loading:\\n                // highlight-start\\n                Text(\\\"Loading...\\\")\\n            // highlight-end\\n            case .success(let data):\\n                // highlight-start\\n                ForEach(data) { podcast  in\\n                    Text(podcast.title)\\n                }\\n            // highlight-end\\n            case .failure:\\n                // highlight-start\\n                Text(\\\"Failure\\\")\\n            // highlight-end\\n            case .none:\\n                // highlight-start\\n                Text(\\\"Loading...\\\")\\n            // highlight-end\\n            }\\n        }\\n    }\\n}\\n\\nstruct ContentView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        ContentView()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson4ContentViewUpdate5\\n// $$liquidcoderFilename-ContentView\\n\\nimport SwiftUI\\n\\nstruct ContentView: View {\\n\\n    @StateObject private var store = Store()\\n\\n    private let term = \\\"sport\\\"\\n\\n    var body: some View {\\n        List {\\n            switch store.apiState.preferredPodcasts[term] {\\n            case .loading:\\n                Text(\\\"Loading...\\\")\\n            case .success(let data):\\n                ForEach(data) { podcast  in\\n                    Text(podcast.title)\\n                }\\n            case .failure:\\n                Text(\\\"Failure\\\")\\n            case .none:\\n                Text(\\\"Loading...\\\")\\n            }\\n            // highlight-start\\n        }.onAppear {\\n            store.dispatch(.api(.fetchPodcasts(term, limit: 10)))\\n        }\\n        // highlight-end\\n    }\\n}\\n\\nstruct ContentView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        ContentView()\\n    }\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson4Introduction-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey there, welcome back! In the previous lesson, we created the networking service with other helpers. In this one, we are going to make use of that networking service when we create the store which will be the center of operations for most of the app.  Let's get started!\")), mdx(\"div\", {\n    id: \"Lesson4LoggingUtils-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Logging utils\"), mdx(\"p\", null, \"Before we get started, let's create a simple utility struct to help us with logging. In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Shared\")), \" folder, add another folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Utils,\"), \" and inside it a swift file containing the following\"), mdx(\"p\", null, \"We will talk about this logging another day, right now let's focus on the task at hand.\")), mdx(\"div\", {\n    id: \"Lesson4Redux1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"SwiftUI - Redux\"), mdx(\"p\", null, \"Redux is design pattern mainly used in react applications. It's a pretty clever way of managing state in a sense that every mutation to the it is made in one place, but propagated to as many listeners as there can be. Of course, we will not go overboard with the design because I believe that the more abstract you want to make things, the more complicated they become, so the goal of this lesson is to leverage the centralisation of redux and reactivity of observables  while using the simplest form of redux to avoid introducing too much abstraction.\")), mdx(\"div\", {\n    id: \"Lesson4Action1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Action\"), mdx(\"p\", null, \"Create a new folder inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Shared\")), \" one named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Store.\"), \" This is where we will put everything store related starting with actions, so in that folder add a new swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Action.\"))), mdx(\"div\", {\n    id: \"Lesson4Action2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"As you can see this is an enum containing another enum. The main action will contain more sub-actions which, in turn, will have cases that will reflects various actions that a user might perform. \"), mdx(\"p\", null, \"The first action we need is an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Api\"), \" one, we will add more actions later on. \")), mdx(\"div\", {\n    id: \"Lesson4Action3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \" The main action will also have cases for each sub-action that it contains, the first one being \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"api\"), \" for the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Api\"), \" enum.\"), mdx(\"p\", null, \"Don't worry if you don't understand right now, It will all make sense once we start using it.\")), mdx(\"div\", {\n    id: \"Lesson4AppState1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"App State\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Store\"), \" folder, add a new swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"AppState\"), \".\")), mdx(\"p\", null, \"Just like the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Action\"), \" , we will wrap the various  states in an enum.\")), mdx(\"div\", {\n    id: \"Lesson4AppState2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Inside the enum, we first create a new \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Result\"), \" type, the one that comes with \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"swift\")), \" didn't work quite well for our project as it only has 2 cases. Our \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Result\"), \" type need to conform to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Equatable\"), \" protocol, reason of which you will understand later, and as a requirement we need to implement the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"==\"), \" function.\")), mdx(\"div\", {\n    id: \"Lesson4AppState3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next, we add the first state which is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Api\"), \".  We will add to that struct as we go along , and just like the action, we will add more states as we go along.\")), mdx(\"div\", {\n    id: \"Lesson4AppEnv1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"App Environment\"), mdx(\"p\", null, \"This environment is different from what we have talked about in the first lesson, this one is for \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Redux\"), \".\"), \" The App environment will  hold our services that the store need to perform various operations. In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Store\")), \" folder, add a swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"AppEnvironment\"), \".\"))), mdx(\"div\", {\n    id: \"Lesson4AppEnv2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"As you can see, we have few properties in there. The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiService\"), \"  that we worked on in the previous lesson, and a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"JsonDecoder\"), \" object declaration. We are making sure they can be injected easily into the environment, hence the use of protocols here. This struct will also be added on as we go.\")), mdx(\"div\", {\n    id: \"Lesson4ApiReducer1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Api Reducer\"), mdx(\"p\", null, \"A reducer is just a function where all the work get done based on the type of action that was dispatch.  In the same folder, add another swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"ApiReducer.\"))), mdx(\"div\", {\n    id: \"Lesson4ApiReducer2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We first need to bring in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Combine\"), \".  Then we declare a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"typealias\"), \" to use for the state completion closure, this is to simplify the method signature in which this completion will be used.\"), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"execute\"), \" function is the the entry point, it will accept API Related action and state as this is an Api reducer, and will return \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AnyCancellable\"), \" created by publisher that we will use in the function.\")), mdx(\"div\", {\n    id: \"Lesson4ApiReducer3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We create a publisher for each case in the execute function using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiService\"), \" object in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppEnvironment\"), \". As you can see, we will fetch podcasts from the internet using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"remotePublisher\"), \" whereas in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"localPublisher\"), \" we are just reading the genres file located in the project to fetch genres.\")), mdx(\"div\", {\n    id: \"Lesson4ApiReducer4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We subscribe to the publishers using the the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"subscribe\"), \" function that we will create next, inside its completion block we get a result and an error.\"), mdx(\"p\", null, \" We will then use that result to update the state in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"setPreferredPodcasts\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"setAllGenres\"), \" functions. We will create those missing functions shortly.\")), mdx(\"div\", {\n    id: \"Lesson4ApiReducer5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"As you can see, the subscribe function is generic to allow the use of different type of result, in our case, we will subscribe to both the remote and local publishers which , as you saw, handle different type of struct ( \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastLis\"), \"t and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreList\"), \" ).  \")), mdx(\"div\", {\n    id: \"Lesson4ApiReducer6-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We decode the json data coming from the publisher using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"decode\"), \" operator obviously, passing in the type we want to parse the json in, and the decoder that we declared in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppEnvironment\"), \".\")), mdx(\"div\", {\n    id: \"Lesson4ApiReducer7-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We need to receive the parsed value on the main thread because the value will be consumed by the UI, and if you did not know that updating the UI on a background thread is prohibited, now you know.\")), mdx(\"div\", {\n    id: \"Lesson4ApiReducer8-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"sink\"), \" operator receive the value and passes it to the completion block to be handled by the calling function. This operator has 2 block, the first has a completion status to notify us if the process completed successfully or failed. We are interested in the failed status, so we check if the status is a failure, then we call the completion block if it is, otherwise we move to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"receiveValue\"), \" block.\")), mdx(\"div\", {\n    id: \"Lesson4ApiReducer9-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And last, but not least, these 2 functions just update the state with success or failure responses, and return them via the completion block.\"), mdx(\"p\", null, \"You may be wondering why we need the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"copyState\"), \" instead of using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"inout\"), \" , right? We need to create a new copy of the state that can be modified inside the body of the function, and using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"inout\"), \" is not an option because we are using these 2 functions in a callback closure.\")), mdx(\"div\", {\n    id: \"Lesson4Store1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Store\"), mdx(\"p\", null, \"This is the main center of action. This is where all incoming actions will be received and propagated to corresponding reducers. In the same folder, create a new file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Store\"), \".\"))), mdx(\"div\", {\n    id: \"Lesson4Store2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We declare the state with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"@Published\"), \" property wrapper to notify views that will use it whenever it changes. We are also making it impossible to change the state from outside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Store\"), \" class by giving it the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"private(set)\"), \" keyword which means it will only be privately set.\")), mdx(\"div\", {\n    id: \"Lesson4Store3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We also create a set of \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Subscriptions\"), \" to store \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AnyCancellable\"), \" objects returned by publishers.\")), mdx(\"div\", {\n    id: \"Lesson4Store4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The third property does not need much introduction, we have already talked enough about  it.\"), mdx(\"p\", null, \"The dispatch function will call appropriate reducers based on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Action\"), \" emitted by views. For now, as you can see, we only have one action ( the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"api\"), \" one) which we respond to by calling the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiReducer\"), \"'s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"execute\"), \" method. The execute method of \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiReducer\"), \"  returns a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AnyCancellable\"), \" that we store in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Subscriptions\"), \" Set. Inside the completion block we update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"apiState\"), \" with the new state\")), mdx(\"div\", {\n    id: \"Lesson4ContentViewUpdate1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Checking the api integration\"), mdx(\"p\", null, \"To check if the code we've written so far works, let us modify the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ContentView\"), \" one more time. So to start, replace the entire content of  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ContentView\"), \" with the following\")), mdx(\"div\", {\n    id: \"Lesson4ContentViewUpdate2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The store is declared as a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"StateObject\"), \" inside \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ContentView\"), \" , but this will change. The term is just a placeholder that we will use for testing purposes, terms will be genres that the user has selected.\")), mdx(\"div\", {\n    id: \"Lesson4ContentViewUpdate3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"List\"), \" , we switch on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"preferredPodcasts\"), \" state for \\\"\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"technology\"), \"\\\" because this is the term that we are using to fetch podcasts. \")), mdx(\"div\", {\n    id: \"Lesson4ContentViewUpdate4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"If it's loading, we just display a loading text. When we get some data back in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"success\"), \", we display a list of podcast titles. For the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"failure\"), \" case, we display a failure text.\"), mdx(\"p\", null, \"Right now, if you run the app, you will just see \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"\\\"Loading...\\\",\"), \" this is because , we have dispatched any action to fetch podcasts yet, let's do that now.\")), mdx(\"div\", {\n    id: \"Lesson4ContentViewUpdate5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onAppear\"), \", we dispatch an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Api\"), \" \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fetchPodcast\"), \" podcast action to the store. This action will fetch 10 podcasts related to \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"technology,\"), \" and it will update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"apiState\"), \"'s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"preferredPodcasts\"), \" dictionary field which will also notify the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ContentView\"), \"  to update its views.\"), mdx(\"p\", null, \"And with this guys, part lesson 4 is done, see you in 5. Happy coding!!\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#logging-utils","title":"Logging utils"},{"url":"#swiftui---redux","title":"SwiftUI - Redux"},{"url":"#action","title":"Action"},{"url":"#app-state","title":"App State"},{"url":"#app-environment","title":"App Environment"},{"url":"#api-reducer","title":"Api Reducer"},{"url":"#store","title":"Store"},{"url":"#checking-the-api-integration","title":"Checking the api integration"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Hey there, welcome back! In the previous lesson, we created the networking service with other helpers. In this one, we are going to make use of that networking service when we create the store which will be the center of operations for most of the app.  Let's get started!\"), mdx(\"h2\", null, \"Logging utils\"), mdx(\"p\", null, \"Before we get started, let's create a simple utility struct to help us with logging. In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Shared\")), \" folder, add another folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Utils,\"), \" and inside it a swift file containing the following:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Log.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\nimport SwiftUI\\n\\nextension Date {\\n    func toString() -> String {\\n        return Log.dateFormatter.string(from: self as Date)\\n    }\\n}\\n\\nenum LogEvent: String {\\n    case error = \\\"\\uD83D\\uDCA5\\uD83D\\uDCA5\\uD83D\\uDCA5\\\"\\n    case info = \\\"\\u2139\\uFE0F\\u2139\\uFE0F\\u2139\\uFE0F\\\"\\n    case debug = \\\"\\uD83D\\uDEE0\\uD83D\\uDEE0\\uD83D\\uDEE0\\\"\\n    case verbose = \\\"\\uD83D\\uDCE3\\uD83D\\uDCE3\\uD83D\\uDCE3\\\"\\n    case warning = \\\"\\u26A0\\uFE0F\\u26A0\\uFE0F\\u26A0\\uFE0F\\\"\\n    case severe = \\\"\\uD83D\\uDD25\\uD83D\\uDD25\\uD83D\\uDD25\\\"\\n}\\n\\nstruct Log {\\n\\n    static private var dateFormat = \\\"yyyy-MM-dd hh:mm\\\"\\n    static var dateFormatter: DateFormatter {\\n        let formatter = DateFormatter()\\n        formatter.dateFormat = dateFormat\\n        formatter.locale = Locale.current\\n        formatter.timeZone = TimeZone.current\\n        return formatter\\n    }\\n\\n    private init() {}\\n\\n    static func message(forEvent event: LogEvent, _ object: Any...,\\n                       filename: String = #file,\\n                       line: Int = #line,\\n                       funcName: String = #function) {\\n        #if DEBUG\\n        var objects = \\\"\\\"\\n        object.forEach({objects += \\\"\\\\($0) \\\"})\\n\\n        print(\\\"\\\\(Date().toString()) \\\\(event.rawValue)[\\\\(sourceFileName(filePath: filename))]: \\\\(line) \\\\(funcName) -> \\\\(objects)\\\")\\n        #endif\\n    }\\n\\n        private static func sourceFileName(filePath: String) -> String {\\n        let components = filePath.components(separatedBy: \\\"/\\\")\\n        return components.isEmpty ? \\\"\\\" : components.last!\\n    }\\n\\n    static func error(_ object: Any...) {\\n\\n        message(forEvent: .error, object)\\n    }\\n\\n    static func info(_ object: Any...) {\\n\\n        message(forEvent: .info, object)\\n\\n    }\\n\\n    static func warning(_ object: Any...) {\\n\\n        message(forEvent: .warning, object)\\n\\n    }\\n\\n    static func debug(_ object: Any...) {\\n\\n        message(forEvent: .debug, object)\\n    }\\n\\n    static func verbose(_ object: Any...) {\\n\\n        message(forEvent: .verbose, object)\\n\\n    }\\n\\n    static func severe(_ object: Any...) {\\n\\n        message(forEvent: .severe, object)\\n    }\\n}\\n\")), mdx(\"p\", null, \"We will talk about this logging another day, right now let's focus on the task at hand.\"), mdx(\"h2\", null, \"SwiftUI - Redux\"), mdx(\"p\", null, \"Redux is design pattern mainly used in react applications. It's a pretty clever way of managing state in a sense that every mutation to the it is made in one place, but propagated to as many listeners as there can be. Of course, we will not go overboard with the design because I believe that the more abstract you want to make things, the more complicated they become, so the goal of this lesson is to leverage the centralisation of redux and reactivity of observables  while using the simplest form of redux to avoid introducing too much abstraction.\"), mdx(\"h2\", null, \"Action\"), mdx(\"p\", null, \"Create a new folder inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Shared\")), \" one named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Store.\"), \" This is where we will put everything store related starting with actions, so in that folder add a new swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Action.\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Action.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\n\\nenum Action {\\n    enum Api {\\n        case fetchPodcasts(_ term: String, limit: Int = 10)\\n        case fetchGenres(_ filename: String)\\n    }\\n\\n    case api(_ action: Api)\\n\\n}\\n\")), mdx(\"p\", null, \"As you can see this is an enum containing another enum. The first action we need is an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Api\"), \" one, we will add more actions later on. The main action will contains more sub-actions which, in turn, will have cases that will reflects various actions that a user might perform.  The main action will also have cases for each sub-action that it contains, the first one being \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"api\"), \" for the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Api\"), \" enum. It will all make sense once we start using it.\"), mdx(\"h2\", null, \"App State\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Store\"), \" folder, add a new swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GlobalState\"), \".\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GlobalState.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\nimport SwiftUI\\n\\nenum AppState {\\n\\n    enum Result<T: Equatable>: Equatable {\\n        static func == (lhs: AppState.Result<T>, rhs: AppState.Result<T>) -> Bool {\\n\\n            switch lhs {\\n            case .loading:\\n                if case loading = rhs {\\n                    return true\\n                }\\n            case .success(let lhsValue):\\n                if case success(let rhsValue) = rhs {\\n                    return rhsValue == lhsValue\\n                }\\n\\n            case .failure:\\n                if case failure = rhs {\\n                    return true\\n                }\\n            }\\n\\n            return false\\n        }\\n\\n        case loading\\n        case success(T)\\n        case failure\\n    }\\n\\n    struct  Api {\\n        var preferredPodcasts: [String: Result<[PodcastViewModel]>] = [:]\\n        var allGenres: Result<[GenreViewModel]> = .loading\\n    }\\n}\\n\")), mdx(\"p\", null, \"Here is what we are doing here:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We wrap the entire state in an enum which plays the role of a namespace\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Inside it, we first create a new \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Result\"), \" type, the one that comes with \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"swift\")), \" didn't work quite well for our project as it only has 2 cases. Our \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Result\"), \" type need to conform to the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Equatable\"), \" protocol, reason of which you will understand later, and as a requirement we need to implement the  \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"==\"), \" function.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Next, we add the first state which is the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Api\"), \".  We will add to that struct as we go along , and just like the action, we will add more states as we go along.\")), mdx(\"h2\", null, \"App Environment\"), mdx(\"p\", null, \"This environment is different from what we have talked about in the first lesson, this one is for \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Redux\"), \".\"), \" The App environment will  hold our services that the store need to perform various operations. In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Store\")), \" folder, add a swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"AppEnvironment\"), \".\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"AppEnvironment.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\n\\nstruct AppEnvironment {\\n    let decoder: JSONDecoder\\n    let api: ApiServiceProtocol\\n\\n    init(decoder: JSONDecoder = .init(), api: ApiServiceProtocol = ApiService()) {\\n        self.api = api\\n        self.decoder = decoder\\n    }\\n}\\n\")), mdx(\"p\", null, \"As you can see, we have few properties in there. The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiService\"), \"  that we worked on in the previous lesson, and a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"JsonDecoder\"), \" object declaration. We are making sure they can be injected easily into the environment, hence the use of protocols here. This class will also be added on as we go.\"), mdx(\"h2\", null, \"Api reducer\"), mdx(\"p\", null, \"A reducer is just a function where all the work get done based on the type of action that was dispatch.  In the same folder, add another swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"ApiReducer.\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ApiReducer.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct ApiReducer {\\n    typealias StateCompletion = ((AppState.Api\\n    ) -> Void)\\n\\n    // MARK: - execute\\n    static func execute(action: Action.Api, state: AppState.Api, environment: AppEnvironment, completion:  @escaping StateCompletion ) -> AnyCancellable {\\n\\n        switch action {\\n        case .fetchPodcasts(let term, let limit):\\n            let publisher = environment.api.remotePublisher(term: term, limit: limit)\\n            return subscribe(to: publisher, type: PodcastList.self, environment: environment) { result, _ in\\n               setPreferredPodcasts(state: state, term: term, result: result, completion: completion)\\n            }\\n\\n        case .fetchGenres(let filename):\\n            let publisher = environment.api.localPublisher(filename: filename)\\n            return subscribe(to: publisher, type: GenreList.self, environment: environment) { result, _ in\\n                setAllGenres(state: state, result: result, completion: completion)\\n            }\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"execute\"), \" function is the the entry point. We will pass in a sub-action, in this case \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Api\"), \", based on that action,  we will create a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"publisher\"), \" to which we subscribe using the the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"subscribe\"), \" function that we will create next, inside its completion block we get a result and an error.  We will then use that result to update the state in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"setPreferredPodcasts\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"setAllGenres\"), \" functions. We will create those missing functions shortly.\"), mdx(\"p\", null, \"Add this in \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ApiReducer.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" // MARK: - subscribe\\nprivate static func subscribe<T: Codable>(to publisher: AnyPublisher<Data, URLError>, type: T.Type, environment: AppEnvironment, completion: @escaping ( T?, Error?) -> Void) -> AnyCancellable {\\n    publisher\\n        .decode(type: type, decoder: environment.decoder)\\n        .receive(on: DispatchQueue.main)\\n        .sink { status in\\n            if case .failure(let error) = status {\\n                Log.error(error)\\n                completion(nil, error)\\n            }\\n        } receiveValue: { result in\\n            completion(result, nil)\\n        }\\n}\\n\")), mdx(\"p\", null, \"As you can see, the subscribe function is generic to allow the use of different type of result.  Here is a break-down of what we have done in the function.\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We will get data from the publisher. We then use the decode operator to parse it into a corresponding type using the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"JSONDecoder\"), \" from the environment. \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We need to receive the parsed value on the main thread because the value will be consumed by the UI, and if you did not know that updating the UI on a background thread is prohibited, now you know.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"sink\"), \" operator receive the value and passes it to the completion block to be handled by the calling function. This operator has 2 block, the first has a completion status to notify us if the process completed successfully or failed. We are interested in the failed status, so we check if the status is a failure, then we call the completion block if it is, otherwise we move to the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"receiveValue\"), \" block.\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ApiReducer.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// MARK: - setAllGenres\\nprivate static func setAllGenres(state: AppState.Api, result: GenreList?, completion:  @escaping StateCompletion) {\\n    var copyState = state\\n    if let genreList = result {\\n        copyState.allGenres = .success(genreList.genres.map { GenreViewModel($0) })\\n    } else {\\n        copyState.allGenres = .failure\\n    }\\n\\n    completion(copyState)\\n}\\n\\n// MARK: - setPreferredPodcasts\\nprivate static func setPreferredPodcasts( state: AppState.Api, term: String, result: PodcastList?, completion:  @escaping StateCompletion) {\\n    var copyState = state\\n\\n      if let podcastList = result {\\n          copyState.preferredPodcasts[term] = .success(podcastList.results\\n                                                      .map { PodcastViewModel(podcast: $0) }\\n                                                      .filter { !$0.feedUrl.isEmpty && $0.totalEpisodes > 0 })\\n      } else {\\n          copyState.preferredPodcasts[term] = .failure\\n      }\\n\\n      completion(copyState)\\n}\\n\")), mdx(\"p\", null, \"These 2 functions just update the state with success or failure responses, and return them via the completion block.\"), mdx(\"h2\", null, \"Store\"), mdx(\"p\", null, \"This is the main center of action. This is where all incoming actions will be received and propagated to corresponding reducers. In the same folder, create a new file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Store\"), \".\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Store.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\nimport Combine\\n\\nclass Store: ObservableObject {\\n\\n    @Published private(set) var apiState = AppState.Api()\\n\\n    private var subscriptions = Set<AnyCancellable>()\\n\\n    private let environment: AppEnvironment\\n\\n    init(environment: AppEnvironment = AppEnvironment() ) {\\n        self.environment = environment\\n    }\\n\\n    func dispatch(_ action: Action) {        \\n        switch action {\\n        case .api(let action):\\n            ApiReducer.execute(action: action, state: apiState, environment: environment) { [weak self] state in\\n                self?.apiState = state\\n            }.store(in: &subscriptions)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"Here is what we have done above.\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We declare the state with \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"@Published\"), \" property wrapper to notify views that will use it whenever it changes. We are also making it impossible to change the state from outside the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Store\"), \" class by giving it the  \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"private(set)\"), \" keyword which means it will only be privately set.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We also create a set of \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Subscriptions\"), \" to store cancellables in returned by publishers.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The third property does not need much introduction, we have already talked enough about  it.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The dispatch function will call appropriate reducers based of the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Action\"), \" emitted by views. For now, as you can see, we only have one action ( the api one) which we respond to by calling the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ApiReducer\"), \"'s \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"execute\"), \" method. The execute method of \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ApiReducer\"), \"  returns a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"AnyCancellable\"), \" that we store in the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Subscriptions\"), \" Set.\")), mdx(\"h2\", null, \"Checking the api integration\"), mdx(\"p\", null, \"To check if the code we 've written so far works, let us modify the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ContentView\"), \" one more time. So replace the entire content of  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ContentView\"), \" with the following:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ContentView.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct ContentView: View {\\n\\n    @StateObject private var store = Store()\\n\\n    private let term = \\\"technology\\\"\\n\\n    var body: some View {\\n        List {\\n            switch store.apiState.preferredPodcasts[term] {\\n            case .loading:\\n                Text(\\\"Loading...\\\")\\n            case .success(let data):\\n                ForEach(data) { podcast  in\\n                    Text(podcast.title)\\n                }\\n            case .failure:\\n                Text(\\\"Failure\\\")\\n            case .none:\\n                Text(\\\"Loading...\\\")\\n            }\\n        }.onAppear {\\n            store.dispatch(.api(.fetchPodcasts(term, limit: 10)))\\n        }\\n    }\\n}\\n\\nstruct ContentView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        ContentView()\\n    }\\n}\\n\")), mdx(\"p\", null, \"Here is what you need to know here:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The store is declared as a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"StateObject\"), \" inside \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ContentView\"), \" , but this will change.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Inside the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"List\"), \" , we switch on the preferred podcasts state for \\\"\", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"technology\"), \"\\\" because this is the term that we are using to fetch podcasts. \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"If it's loading, we just display a loading text. When we get some data back in \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"success\"), \", we display a list of podcast titles. For the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"failure\"), \" case, we display a failure text.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onAppear\"), \", we dispatch an \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Api\"), \" \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"fetchPodcast\"), \" podcast action to the store.\")), mdx(\"p\", null, \"And with this guys, part lesson 4 is done, see you in 5. Happy coding!!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#logging-utils","title":"Logging utils"},{"url":"#swiftui---redux","title":"SwiftUI - Redux"},{"url":"#action","title":"Action"},{"url":"#app-state","title":"App State"},{"url":"#app-environment","title":"App Environment"},{"url":"#api-reducer","title":"Api reducer"},{"url":"#store","title":"Store"},{"url":"#checking-the-api-integration","title":"Checking the api integration"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-01-28T17:20:50.359Z","isNewLayout":true,"previews":{"previews":"{\n    \"Lesson5BodyText1\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1612458383/StreamCast/vvpucexe5dmcvgqhjh3u.png\" ],\n\t  \"Lesson5GetStartedButton3\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1611828191/StreamCast/i9hlatqfbs8mgwyo4lat.png\" ],\n\t\t\"Lesson5GenreToggle4\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1611828515/StreamCast/qzqj1t5j0y0y1hkzbdiz.png\" ],\n\t\t\"Lesson5GenreSelectionCollection4\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1611828822/StreamCast/xm8uivdsxw5zdn5l72sh.png\" ],\n\t\t\"Lesson5GenreSelectionHeader1\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1611829119/StreamCast/xvtwkivla6qevfr1tksp.png\" ],\n \"Lesson5GenreSelectionContent4\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1611829523/StreamCast/v5hom6ssy3jy8z3eb7zm.png\" ],\n \"Lesson5GenreSelectionScreen5\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1612458826/StreamCast/yyugoyytdue5tknb0c1t.png\" ]\n}"},"slug":"lesson-5-genre-selection-screen","title":"Lesson #5: Genre selection screen","updatedDate":"2021-01-28T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZUB8bXZFNEzarkSLp8EEJBEeY1OgkJiYSEk","type":"zip","id":"60fd5632-d1e0-5c2e-88a6-7e45945e0f8f","name":"Source code"}],"allChapters":["Lesson5Introduction: Introduction","Lesson5ProjectLayout1: Project layout","Lesson5ProjectLayout2: Color sets and Color extension","Lesson5AccentButtonStyle1: Accent button style","Lesson5ButtonToggleStyle1: Button Toggle Style","Lesson5BodyText1: BodyText","Lesson5GetStartedButton1: Get started button","Lesson5GenreToggle1: Genre Toggle","Lesson5GenreSelectionCollection1: Genre Selection Collection","Lesson5GenreSelectionHeader1: Genre Selection Header","Lesson5GenreSelectionContent1: Genre Selection Content","Lesson5GenreSelectionScreen1: Genre Selection Screen"],"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson5ProjectLayout2\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611827974/StreamCast/cwh5akihfwhbcr46pu3t.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611827974/StreamCast/cwh5akihfwhbcr46pu3t.png\"\n  })))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5AccentButtonStyle1\\n// $$liquidcoderFilename-AccentButtonStyle\\n\\nimport SwiftUI\\n\\n// highlight-start\\nstruct AccentButtonStyle: ButtonStyle {\\n\\n}\\n// highlight-end\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5AccentButtonStyle2\\n// $$liquidcoderFilename-AccentButtonStyle\\n\\nimport SwiftUI\\n\\nstruct AccentButtonStyle: ButtonStyle {\\n\\n// highlight-start\\n     var verticalPadding: CGFloat\\n// highlight-end   \\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5AccentButtonStyle3\\n// $$liquidcoderFilename-AccentButtonStyle\\n\\nimport SwiftUI\\n\\nstruct AccentButtonStyle: ButtonStyle {\\n\\n    var verticalPadding: CGFloat\\n\\n    // highlight-start\\n    func makeBody(configuration: Self.Configuration) -> some View {\\n\\n    }\\n    // highlight-end\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5AccentButtonStyle4\\n// $$liquidcoderFilename-AccentButtonStyle\\n\\nimport SwiftUI\\n\\nstruct AccentButtonStyle: ButtonStyle {\\n\\n    var verticalPadding: CGFloat\\n\\n    func makeBody(configuration: Self.Configuration) -> some View {\\n\\n        // highlight-start\\n        let horizontalPadding = verticalPadding * 2\\n\\n        return configuration.label\\n            .padding(.horizontal, horizontalPadding)\\n            .padding(.vertical, verticalPadding)\\n        // highlight-end\\n    }\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5AccentButtonStyle5\\n// $$liquidcoderFilename-AccentButtonStyle\\n\\nimport SwiftUI\\n\\nstruct AccentButtonStyle: ButtonStyle {\\n\\n    var verticalPadding: CGFloat\\n\\n    func makeBody(configuration: Self.Configuration) -> some View {\\n\\n        let horizontalPadding = verticalPadding * 2\\n\\n        return configuration.label\\n            .padding(.horizontal, horizontalPadding)\\n            .padding(.vertical, verticalPadding)\\n            // highlight-start\\n            .foregroundColor( Color.background)\\n            .background(Color.accentColor)\\n        // highlight-end\\n    }\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5AccentButtonStyle6\\n// $$liquidcoderFilename-AccentButtonStyle\\n\\nimport SwiftUI\\n\\nstruct AccentButtonStyle: ButtonStyle {\\n\\n    var verticalPadding: CGFloat\\n\\n    func makeBody(configuration: Self.Configuration) -> some View {\\n\\n        let horizontalPadding = verticalPadding * 2\\n\\n        return configuration.label\\n            .padding(.horizontal, horizontalPadding)\\n            .padding(.vertical, verticalPadding)\\n            .foregroundColor( Color.background)\\n            .background(Color.accentColor)\\n            // highlight-start\\n            .cornerRadius(15)\\n            .scaleEffect(configuration.isPressed ? 0.9 : 1.0)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5ColorExt\\n// $$liquidcoderFilename-AccentButtonStyle\\n\\nimport SwiftUI\\n\\nextension Color {\\n    static let pop = Color(\\\"pop\\\")\\n    static let background = Color(\\\"background\\\")\\n    static let overlay = Color(\\\"overlay\\\")\\n    static let text = Color(\\\"text\\\")\\n    static let shadow = Color(\\\"shadow\\\")\\n    #if os(iOS)\\n    static let body = Color(UIColor.secondaryLabel)\\n    static let border = Color(UIColor.separator)\\n    #elseif os(macOS)\\n    static let body = Color(NSColor.secondaryLabelColor)\\n    static let border = Color(NSColor.separatorColor)\\n    #endif\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5ButtonToggleStyle2\\n// $$liquidcoderFilename-ButtonToggleStyle\\n\\nimport SwiftUI\\n\\nstruct ButtonToggleStyle: ToggleStyle {\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5ButtonToggleStyle3\\n// $$liquidcoderFilename-ButtonToggleStyle\\n\\nimport SwiftUI\\n\\nstruct ButtonToggleStyle: ToggleStyle {\\n\\n// highlight-start\\n     var cornerRadius: CGFloat\\n// highlight-end\\n\\n    func makeBody(configuration: Configuration) -> some View {\\n\\n        // highlight-start\\n        configuration.label\\n            .font(.headline)\\n            .padding()\\n            .foregroundColor(configuration.isOn ? Color.accentColor : Color.gray)\\n            .frame(maxWidth: .infinity)\\n            .background(configuration.isOn ? Color.accentColor.opacity(0.2) : Color.overlay)\\n            .cornerRadius(cornerRadius)\\n// highlight-end\\n\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5ButtonToggleStyle4\\n// $$liquidcoderFilename-ButtonToggleStyle\\n\\nimport SwiftUI\\n\\nstruct ButtonToggleStyle: ToggleStyle {\\n\\n    var cornerRadius: CGFloat\\n\\n    func makeBody(configuration: Configuration) -> some View {\\n\\n        configuration.label\\n            .font(.headline)\\n            .padding()\\n            .foregroundColor(configuration.isOn ? Color.accentColor : Color.gray)\\n            .frame(maxWidth: .infinity)\\n            .background(configuration.isOn ? Color.accentColor.opacity(0.2) : Color.overlay)\\n            .cornerRadius(cornerRadius)\\n            // highlight-start\\n            .onTapGesture {\\n                configuration.isOn.toggle()\\n            }\\n        // highlight-end\\n\\n    }\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5BodyText1\\n// $$liquidcoderFilename-BodyText\\n\\nimport SwiftUI\\n\\nstruct BodyText: View {\\n\\n    var value: String\\n\\n    var body: some View {\\n        Text(value)\\n            .font(.body)\\n            .foregroundColor(.body)\\n    }\\n}\\n\\nstruct BodyText_Previews: PreviewProvider {\\n    static var previews: some View {\\n        BodyText(value: \\\"Hello world\\\")\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5GetStartedButton1\\n// $$liquidcoderFilename-GetStartedButton\\n\\nimport SwiftUI\\n\\nstruct GetStartedButton: View {\\n\\n    var body: some View {\\n        Text(\\\"Hello world\\\")\\n    }\\n}\\n\\nstruct GetStartedButton_Previews: PreviewProvider {\\n    static var previews: some View {\\n        GetStartedButton()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5GetStartedButton2\\n// $$liquidcoderFilename-GetStartedButton\\n\\nimport SwiftUI\\n\\nstruct GetStartedButton: View {\\n\\n    // highlight-start\\n    var action = {}\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        Button(action: action, label: {\\n            Text(\\\"Get started\\\")\\n        })\\n        // highlight-end\\n    }\\n}\\n\\nstruct GetStartedButton_Previews: PreviewProvider {\\n    static var previews: some View {\\n        GetStartedButton()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5GetStartedButton3\\n// $$liquidcoderFilename-GetStartedButton\\n\\nimport SwiftUI\\n\\nstruct GetStartedButton: View {\\n\\n    var action = {}\\n\\n    var body: some View {\\n\\n        Button(action: action, label: {\\n            Text(\\\"Get started\\\")\\n            // highlight-start\\n        }).buttonStyle(AccentButtonStyle(verticalPadding: 15))\\n        // highlight-end\\n    }\\n}\\n\\nstruct GetStartedButton_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            GetStartedButton()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            GetStartedButton()\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5GenreToggle1\\n// $$liquidcoderFilename-GenreToggle\\n\\nimport SwiftUI\\n\\nstruct GenreToggle: View {\\n\\n    var body: some View {\\n        Text(\\\"Hello world\\\")\\n    }\\n}\\n\\nstruct GenreToggle_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            GenreToggle(genre: GenreViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            GenreToggle(genre: GenreViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5GenreToggle2\\n// $$liquidcoderFilename-GenreToggle\\n\\nimport SwiftUI\\n\\nstruct GenreToggle: View {\\n\\n      // highlight-start\\n    var genre: GenreViewModel\\n    var onToggle: ((Bool) -> Void) = {_ in }\\n// highlight-end\\n\\n// highlight-start    \\n    @State private var isActive = false        \\n// highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello world\\\")\\n    }\\n}\\n\\nstruct GenreToggle_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            GenreToggle(genre: GenreViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            GenreToggle(genre: GenreViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5GenreToggle3\\n// $$liquidcoderFilename-GenreToggle\\n\\nimport SwiftUI\\n\\nstruct GenreToggle: View {\\n\\n    var genre: GenreViewModel\\n    var onToggle: ((Bool) -> Void) = {_ in }\\n\\n    @State private var isActive = false\\n\\n    var body: some View {\\n        // highlight-start\\n        Toggle(isOn: $isActive, label: {\\n            Text(genre.name)\\n        }).toggleStyle(ButtonToggleStyle(cornerRadius: 30))\\n        // highlight-end\\n    }\\n}\\n\\nstruct GenreToggle_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            GenreToggle(genre: GenreViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            GenreToggle(genre: GenreViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5GenreToggle4\\n// $$liquidcoderFilename-GenreToggle\\n\\nimport SwiftUI\\n\\nstruct GenreToggle: View {\\n\\n    var genre: GenreViewModel\\n    var onToggle: ((Bool) -> Void) = {_ in }\\n\\n    @State private var isActive = false\\n\\n    var body: some View {\\n\\n        Toggle(isOn: $isActive, label: {\\n            Text(genre.name)\\n        }).toggleStyle(ButtonToggleStyle(cornerRadius: 30))\\n        // highlight-start\\n        .onChange(of: isActive, perform: { isActive in\\n            onToggle(isActive)\\n        })\\n        // highlight-end\\n    }\\n}\\n\\nstruct GenreToggle_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            GenreToggle(genre: GenreViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            GenreToggle(genre: GenreViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5GenreSelectionCollection1\\n// $$liquidcoderFilename-GenreSelectionCollection\\n\\nimport SwiftUI\\n\\nstruct GenreSelectionCollection: View {\\n\\n    var body: some View {\\n        Text(\\\"Hello world\\\")\\n    }\\n}\\n\\nstruct GenreSelectionCollection_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            GenreSelectionCollection(genres: GenreViewModel.listPlaceholders, selectedGenres: .constant([:]))\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            GenreSelectionCollection(genres: GenreViewModel.listPlaceholders, selectedGenres: .constant([:]))\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5GenreSelectionCollection2\\n// $$liquidcoderFilename-GenreSelectionCollection\\n\\nimport SwiftUI\\n\\nstruct GenreSelectionCollection: View {\\n\\n  // highlight-start\\n    var genres: [GenreViewModel]\\n    @Binding var selectedGenres: [String: GenreViewModel]\\n// highlight-end\\n\\n// highlight-start    \\n    private let gridItems = Array(repeating: GridItem.init(.adaptive(minimum: 40), spacing: 20), count: 1)       \\n// highlight-end    \\n\\n    var body: some View {\\n        Text(\\\"Hello world\\\")\\n    }\\n}\\n\\nstruct GenreSelectionCollection_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            GenreSelectionCollection(genres: GenreViewModel.listPlaceholders, selectedGenres: .constant([:]))\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            GenreSelectionCollection(genres: GenreViewModel.listPlaceholders, selectedGenres: .constant([:]))\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5GenreSelectionCollection3\\n// $$liquidcoderFilename-GenreSelectionCollection\\n\\nimport SwiftUI\\n\\nstruct GenreSelectionCollection: View {\\n\\n    var genres: [GenreViewModel]\\n    @Binding var selectedGenres: [String: GenreViewModel]\\n\\n    private let gridItems = Array(repeating: GridItem.init(.adaptive(minimum: 40), spacing: 20), count: 1)\\n\\n    var body: some View {\\n        // highlight-start\\n        ScrollView(.horizontal, showsIndicators: false) {\\n            LazyHGrid(rows: gridItems, alignment: .center, spacing: 10) {\\n                ForEach(genres) { genre in\\n\\n                }\\n            }.padding()\\n        }.frame(height: 340, alignment: .center)\\n        // highlight-end\\n    }\\n}\\n\\nstruct GenreSelectionCollection_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            GenreSelectionCollection(genres: GenreViewModel.listPlaceholders, selectedGenres: .constant([:]))\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            GenreSelectionCollection(genres: GenreViewModel.listPlaceholders, selectedGenres: .constant([:]))\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5GenreSelectionCollection4\\n// $$liquidcoderFilename-GenreSelectionCollection\\n\\nimport SwiftUI\\n\\nstruct GenreSelectionCollection: View {\\n\\n    var genres: [GenreViewModel]\\n    @Binding var selectedGenres: [String: GenreViewModel]\\n\\n    private let gridItems = Array(repeating: GridItem.init(.adaptive(minimum: 40), spacing: 20), count: 1)\\n\\n    var body: some View {\\n\\n        ScrollView(.horizontal, showsIndicators: false) {\\n            LazyHGrid(rows: gridItems, alignment: .center, spacing: 10) {\\n                ForEach(genres) { genre in\\n                    // highlight-start\\n                    createGenreToggle(genre)\\n                    // highlight-end\\n                }\\n            }.padding()\\n        }.frame(height: 340, alignment: .center)\\n    }\\n\\n    // highlight-start\\n    fileprivate func createGenreToggle(_ genre: GenreViewModel) -> GenreToggle {\\n        return GenreToggle(genre: genre) { isSelected in\\n            if isSelected {\\n                selectedGenres[genre.name] = genre\\n            } else {\\n                selectedGenres.removeValue(forKey: genre.name)\\n            }\\n        }\\n    }\\n    // highlight-end\\n}\\n\\nstruct GenreSelectionCollection_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            GenreSelectionCollection(genres: GenreViewModel.listPlaceholders, selectedGenres: .constant([:]))\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            GenreSelectionCollection(genres: GenreViewModel.listPlaceholders, selectedGenres: .constant([:]))\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5GenreSelectionHeader1\\n// $$liquidcoderFilename-GenreSelectionHeader\\n\\nimport SwiftUI\\n\\nstruct GenreSelectionHeader: View {\\n    var body: some View {\\n        VStack(alignment: .leading, spacing: 20) {\\n            Text(\\\"Genres\\\")\\n                .font(.largeTitle)\\n                .bold()\\n\\n            BodyText(value: \\\"To get started please choose at least 3 categories that interest you so that we provide you with what like from the start. Go ahead!\\\")\\n        }.padding()\\n    }\\n}\\n\\nstruct GenreSelectionHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            GenreSelectionHeader()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            GenreSelectionHeader()\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5GenreSelectionContent1\\n// $$liquidcoderFilename-GenreSelectionContent\\n\\nimport SwiftUI\\n\\nstruct GenreSelectionContent: View {\\n\\n    var body: some View {\\n       Text(\\\"Hello world\\\")\\n    }\\n}\\n\\nstruct GenreSelectionContent_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n                GenreSelectionContent()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5GenreSelectionContent2\\n// $$liquidcoderFilename-GenreSelectionContent\\n\\nimport SwiftUI\\n\\nstruct GenreSelectionContent: View {\\n\\n    // highlight-start\\n    var genres: [GenreViewModel]\\n    var onContinue: (([GenreViewModel]) -> Void) = {_ in }\\n    // highlight-end\\n\\n    // highlight-start\\n    @State private var selectedGenres: [String: GenreViewModel] = [:]\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello world\\\")\\n    }\\n}\\n\\nstruct GenreSelectionContent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            GenreSelectionContent(genres: GenreViewModel.listPlaceholders)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            GenreSelectionContent(genres: GenreViewModel.listPlaceholders)\\n                .preferredColorScheme(.light)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5GenreSelectionContent3\\n// $$liquidcoderFilename-GenreSelectionContent\\n\\nimport SwiftUI\\n\\nstruct GenreSelectionContent: View {\\n\\n    var genres: [GenreViewModel]\\n    var onContinue: (([GenreViewModel]) -> Void) = {_ in }\\n\\n    @State private var selectedGenres: [String: GenreViewModel] = [:]  \\n\\n    var body: some View {\\n       // highlight-start    \\n    ScrollView(showsIndicators: false) {\\n            VStack(spacing: 30) {\\n                GenreSelectionHeader()\\n                GenreSelectionCollection(genres: genres, selectedGenres: $selectedGenres)\\n            }.padding(.vertical, 40)\\n        }.background(Color.background)\\n            // highlight-end \\n    }\\n}\\n\\nstruct GenreSelectionContent_Previews: PreviewProvider {\\n    static var previews: some View {\\n            Group {\\n            GenreSelectionContent(genres: GenreViewModel.listPlaceholders)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            GenreSelectionContent(genres: GenreViewModel.listPlaceholders)\\n                .preferredColorScheme(.light)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5GenreSelectionContent3\\n// $$liquidcoderFilename-GenreSelectionContent\\n\\nimport SwiftUI\\n\\nstruct GenreSelectionContent: View {\\n\\n    var genres: [GenreViewModel]\\n    var onContinue: (([GenreViewModel]) -> Void) = {_ in }\\n\\n    @State private var selectedGenres: [String: GenreViewModel] = [:]\\n\\n    var body: some View {\\n        // highlight-start\\n        ScrollView(showsIndicators: false) {\\n            VStack(spacing: 30) {\\n                GenreSelectionHeader()\\n                GenreSelectionCollection(genres: genres, selectedGenres: $selectedGenres)\\n            }.padding(.vertical, 40)\\n        }.background(Color.background)\\n        // highlight-end\\n    }\\n}\\n\\nstruct GenreSelectionContent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            GenreSelectionContent(genres: GenreViewModel.listPlaceholders)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            GenreSelectionContent(genres: GenreViewModel.listPlaceholders)\\n                .preferredColorScheme(.light)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5GenreSelectionContent4\\n// $$liquidcoderFilename-GenreSelectionContent\\n\\nimport SwiftUI\\n\\nstruct GenreSelectionContent: View {\\n\\n    var genres: [GenreViewModel]\\n    var onContinue: (([GenreViewModel]) -> Void) = {_ in }\\n\\n    @State private var selectedGenres: [String: GenreViewModel] = [:]\\n\\n    var body: some View {\\n        ScrollView(showsIndicators: false) {\\n            VStack(spacing: 30) {\\n                GenreSelectionHeader()\\n                GenreSelectionCollection(genres: genres, selectedGenres: $selectedGenres)\\n\\n                // highlight-start\\n                if selectedGenres.count > 2 {\\n                    GetStartedButton(action: {\\n                        var genres = [GenreViewModel]()\\n                        selectedGenres.values.forEach { genres.append($0) }\\n                        onContinue(genres)\\n                    })\\n                }\\n                // highlight-end\\n            }.padding(.vertical, 40)\\n        }.background(Color.background)\\n    }\\n}\\n\\nstruct GenreSelectionContent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            GenreSelectionContent(genres: GenreViewModel.listPlaceholders)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            GenreSelectionContent(genres: GenreViewModel.listPlaceholders)\\n                .preferredColorScheme(.light)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5GenreSelectionScreen1\\n// $$liquidcoderFilename-GenreSelectionScreen\\n\\nimport SwiftUI\\n\\nstruct GenreSelectionScreen: View {\\n\\n    var body: some View {\\n       Text(\\\"Hello world\\\")\\n    }\\n}\\n\\nstruct GenreSelectionScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n                GenreSelectionScreen()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5GenreSelectionScreen2\\n// $$liquidcoderFilename-GenreSelectionScreen\\n\\nimport SwiftUI\\n\\nstruct GenreSelectionScreen: View {\\n\\n    // highlight-start\\n    @EnvironmentObject private var store: Store\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello world\\\")\\n    }\\n}\\n\\nstruct GenreSelectionScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            GenreSelectionScreen()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n                // highlight-start\\n                .environmentObject(Store())\\n            // highlight-end\\n            GenreSelectionScreen()\\n                .previewLayout(.sizeThatFits)\\n                // highlight-start\\n                .environmentObject(Store())\\n            // highlight-end\\n        }\\n    }\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5GenreSelectionScreen3\\n// $$liquidcoderFilename-GenreSelectionScreen\\n\\nimport SwiftUI\\n\\nstruct GenreSelectionScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    var body: some View {\\n        // highlight-start\\n        switch store.apiState.allGenres {\\n        case .loading:\\n            break\\n        case .success(let data):\\n            break\\n        case .failure:\\n            break\\n        }\\n        // highlight-end\\n    }\\n}\\n\\nstruct GenreSelectionScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            GenreSelectionScreen()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n                .environmentObject(Store())\\n            GenreSelectionScreen()\\n                .previewLayout(.sizeThatFits)\\n                .environmentObject(Store())\\n        }\\n    }\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5GenreSelectionScreen4\\n// $$liquidcoderFilename-GenreSelectionScreen\\n\\nimport SwiftUI\\n\\nstruct GenreSelectionScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    var body: some View {\\n        switch store.apiState.allGenres {\\n        case .loading:\\n            // highlight-start\\n            GenreSelectionContent(genres: GenreViewModel.listPlaceholders) { _ in\\n            }.onAppear(perform: {\\n                store.dispatch(.api(.fetchGenres(\\\"genres\\\")))\\n            }).redacted(reason: .placeholder)\\n        // highlight-end\\n        case .success(let data):\\n            break\\n        case .failure:\\n            break\\n        }\\n    }\\n}\\n\\nstruct GenreSelectionScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            GenreSelectionScreen()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n                .environmentObject(Store())\\n            GenreSelectionScreen()\\n                .previewLayout(.sizeThatFits)\\n                .environmentObject(Store())\\n        }\\n    }\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson5GenreSelectionScreen5\\n// $$liquidcoderFilename-GenreSelectionScreen\\n\\nimport SwiftUI\\n\\nstruct GenreSelectionScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    var body: some View {\\n        switch store.apiState.allGenres {\\n        case .loading:\\n            GenreSelectionContent(genres: GenreViewModel.listPlaceholders) { _ in\\n            }.onAppear(perform: {\\n                store.dispatch(.api(.fetchGenres(\\\"genres\\\")))\\n            }).redacted(reason: .placeholder)\\n        case .success(let data):\\n            // highlight-start\\n            GenreSelectionContent(genres: data) { _ in\\n\\n            }\\n        // highlight-end\\n        case .failure:\\n            // highlight-start\\n            Text(\\\"Failed to Load genres...\\\")\\n        // highlight-end\\n        }\\n    }\\n}\\n\\nstruct GenreSelectionScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            GenreSelectionScreen()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n                .environmentObject(Store())\\n            GenreSelectionScreen()\\n                .previewLayout(.sizeThatFits)\\n                .environmentObject(Store())\\n        }\\n    }\\n}\\n\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson5Introduction-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey there, welcome to lesson 5. How are you enjoying the course so far? Hope you like it. We have done a ton of stuff over the passed four lessons. In this one, we will create our first screen. So buckle up and let's go!\")), mdx(\"div\", {\n    id: \"Lesson5ProjectLayout1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Project layout\"), mdx(\"p\", null, \"Organising a project and naming files are those things in programming that seem harmless and simple until you try them. It fantastic how my brain can work even when I am asleep, but only stops when it's time name files, lol!... So, in this section I will show you how  we will organise our code. Of course, project layout is subjective, I'll just show what works for me, you are free to modify it however you like. \")), mdx(\"div\", {\n    id: \"Lesson5ProjectLayout2-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Color sets and Color extension\"), mdx(\"p\", null, \"We have already created folders and files, and that's all good. The project structure I want to show you will mainly be for UI.  Here is the layout\"), mdx(\"p\", null, \"The main thing to note in the above image is the 3 folders ( \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"*Styles\"), \", \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Elements, Components\"), \" )\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"*.\"), \" \"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Styles\")), \" will contain all custom styles we will create in the project\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Elements\")), \" will contain simple views, mostly one piece of UI, for example a custom \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Button\"), \" or a styled \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Text\"), \", and so on.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Components\")), \"  are a big chunk of UI with which  may construct a an entire screen when combined.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"We may also need a \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Modifiers\"), \" folder down the line, but we are not there yet.\")), mdx(\"p\", null, \"The \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens\")), \"  are what they imply, they will contain various screen that will compose the entire app.\")), mdx(\"div\", {\n    id: \"Lesson5Colors1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Before working on the UI, we need colors, so let's add them. \"), mdx(\"p\", null, \"Here is a list of colors that we will use:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"background\")), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Dark: #000000\")), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Light: #FFFFFF\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"overlay\")), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Dark: #1C1C1E\")), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Light: #F2F2F7\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"pop\")), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Dark: #F0E416\")), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Light: #F04667\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"shadow\")), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Dark: #000000\")), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Light: seperatorColor\"))))), mdx(\"div\", {\n    id: \"Lesson5Colors2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"If you do not know how to add colors in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"asset catalogue\")), \", let's add the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"shadow\")), \" color together. \"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Assets.xcassets,\")), \" tap the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"\\\"+\\\"\"), \" button and choose  \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Color Set.\"))), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"While selecting the color set you have just added,  click the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"attributes inspector\")), \" on the right-hand side and change the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"appearances\")), \" value to \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Any, Dark.\")), \" The \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Any Appearance\")), \" will be used for the  light theme and the other one for dark theme.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Now select the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Any Appearance\"), \",\"), \" and you should see more options pop up in the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"inspectors.\")), \" For this color set, the light color is \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"seperatorColor\"), \" which a system color, so we do not need to add color manually, just use the \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"content\"), \" dropdown in the C\", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"olor\"), \" section and choose \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"seperatorColor\")), \" in the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"iOS System Colors\")), \" section.\"))), mdx(\"div\", {\n    id: \"Lesson5AccentButtonStyle1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Accent button style\"), mdx(\"p\", null, \"This style will be shared by multiple buttons in different screens, so let's put it in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI/Styles.\")), \" \"), mdx(\"p\", null, \"Add a swift file in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ShareUI/Styles\")), \" folder.\"), mdx(\"p\", null, \"In swiftUI, we can style buttons using custom implementations of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ButtonStyle\"), \" protocol, hence conforming the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AccentButtonStyle\"), \" to it.\")), mdx(\"div\", {\n    id: \"Lesson5AccentButtonStyle2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We will only allow the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"verticalPadding\"), \" to be customisable, the horizontal padding will be twice as big the vertical one.\")), mdx(\"div\", {\n    id: \"Lesson5AccentButtonStyle3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ButtonStyle\"), \" protocol has a required method that returns the body of the button which is a swiftUI View.\")), mdx(\"div\", {\n    id: \"Lesson5AccentButtonStyle4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The configuration has 2 properties, the first one is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"label\"), \" which is the view the button displays and the second is a boolean property that indicated whether the button is currently pressed or not. We then give the label a vertical and horizontal paddings.\")), mdx(\"div\", {\n    id: \"Lesson5AccentButtonStyle5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next , we proceed to adding the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"foregroundColor\"), \" and the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"backgroundColor\"), \" using the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"colors\")), \" that we have yet to create in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Color+\"), \" extension file. Don't worry about the errors, we will create the missing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Color+\"), \" extension shortly\")), mdx(\"div\", {\n    id: \"Lesson5AccentButtonStyle6-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Last, we give it a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cornerRadius\"), \" and a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"scaleEffect\"), \" which is dynamic based on whether the button is  pressed  or not. So this will create a bouncing effect when the button is pressed down.\")), mdx(\"div\", {\n    id: \"Lesson5ColorExt-Section\",\n    className: \"post-section\"\n  }, mdx(\"h3\", null, \"Color+\"), mdx(\"p\", null, \"Create this file in \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Extensions\")), \"  folder, and just check the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"iOS\")), \" and \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"macOS\")), \" targets.\"), mdx(\"p\", null, \"Now put the code on the right inside it. There's nothing special here, we are just extending the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Color\"), \" type.\")), mdx(\"div\", {\n    id: \"Lesson5ButtonToggleStyle1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Button Toggle Style\"), mdx(\"p\", null, \"This style will be used with a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Toggle\"), \" . We want to have a button that can be toggled on and off on demand, likely for us the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Toggle\"), \"  element combined with a custom style in swiftUI is very powerful, so we will use the 2 to create the type of button I have just talked about. \")), mdx(\"div\", {\n    id: \"Lesson5ButtonToggleStyle2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Inside the same folder, add another swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ButtonToggleStyle.\"))), mdx(\"p\", null, \"Just like the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ButtonStyle\"), \", the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ToggleStyle\"), \" protocol also has a label which plays the same role as the button's, but instead of having a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isPressed\"), \" property, it's \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isOn\"), \".\")), mdx(\"div\", {\n    id: \"Lesson5ButtonToggleStyle3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We want to make the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cornerRadius\"), \" customisable, so  we expose it to the calling view.\"), mdx(\"p\", null, \"We then set almost similar styles as the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ButtonStyle\"), \".\")), mdx(\"div\", {\n    id: \"Lesson5ButtonToggleStyle4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The only thing that differs from the button is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onTapGesture\"), \" which is needed in order to toggle the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isOn\"), \" property.\")), mdx(\"div\", {\n    id: \"Lesson5BodyText1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"BodyText\"), mdx(\"p\", null, \"This is a simple help text view that only has a \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"body\"), \" font and color. This will also be used by other views in the project, let's put it in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ShareUI/Elements.\"))), mdx(\"p\", null, \"With that,  we are done with  the shared uis in this particular lesson. Let's create our first screen.\")), mdx(\"div\", {\n    id: \"Lesson5GetStartedButton1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Get started button\"), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GetStartedButton\"), \" will utilise the button style we have just created. Create a folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens,\")), \" and inside it add another one named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreSelection,\")), \" and in that one you will create those 3 folders we've talked about earlier \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"(Styles, Elements, Components)\")), \".  \"), mdx(\"p\", null, \"This  button will only be used  on the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Genre Selection Screen\")), \"  that's why it's inside it unclosing folder. So create a swiftUI file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GetStartedButton\")), \" inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Elements\")), \" folder.\")), mdx(\"div\", {\n    id: \"Lesson5GetStartedButton2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We declare the action closure that we pass in the Button. \")), mdx(\"div\", {\n    id: \"Lesson5GetStartedButton3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Then we set the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AccentButtonStyle\"), \" using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"buttonStyle\"), \" mdifier.\"), mdx(\"hr\", null), mdx(\"h3\", null, \"Previews\"), mdx(\"p\", null, \"In the previews struct, replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GetStartedButton()\"), \" with the highlighted content in the code on the left.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"From now on, this is how our preview code will look like. The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"preferredColorScheme\"), \" is used to change the color scheme of the preview and the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"previewLayout\"), \" shrink or expand the layout based on the provided size. the pre-defined \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"sizeThatFits\"), \" value means that the preview will match exactly the size of the content displayed.\"))), mdx(\"div\", {\n    id: \"Lesson5GenreToggle1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Genre Toggle\"), mdx(\"p\", null, \"This one will use the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ButtonToggleStyle\"), \" in the same exact way we used \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AccentButtonStyle\"), \". So create a new swiftUI file inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Elements.\")), \" Again, we are putting these 2 inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Elements\")), \" folder because they are simple part of a bigger component.\")), mdx(\"div\", {\n    id: \"Lesson5GenreToggle2-Section\",\n    className: \"post-section\"\n  }, mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We declare a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"genre\"), \" view model property\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onToggle\"), \" is a closure that takes in a bool and returns nothing\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"isActive\"), \" is used to update based on the current state of \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"isOn\"), \" binding property of the toggle.\"))), mdx(\"div\", {\n    id: \"Lesson5GenreToggle3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \", we create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Toggle\"), \" , its label being a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" displaying the genre name. We also bind the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isOn\"), \" property to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Toggle\"), \"'s isOn parameter.\"), mdx(\"p\", null, \"Just like the button, We set the style using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"toggleStyle\"), \" modifier.\")), mdx(\"div\", {\n    id: \"Lesson5GenreToggle4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onChange\"), \" modiifer will listen to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isActive\"), \" state, and run the action block whenever it changes that's where we call the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onToggle\"), \" callback with the value of \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isActive\"), \".\")), mdx(\"div\", {\n    id: \"Lesson5GenreSelectionCollection1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Genre Selection Collection\"), mdx(\"p\", null, \"This will contain a horizontal scrollview containing all the genres from which the user can choose his or her preferred ones. This one is a component, so in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Components\")), \" folder, add another swiftUI file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreSelectionCollection.\")))), mdx(\"div\", {\n    id: \"Lesson5GenreSelectionCollection2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We have a genres array property that we will use to populate the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ScrollView\"), \". The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"selectedGenres\"), \" is a dictionary which make it easier and efficient to add and remove elements from it even if the difference in performance is negligeable.\"), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"gridItems\"), \" is an array of adaptive \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GridItem\"), \" objects where the minimum height is 40, this means we will fill as many items as they can fit vertically.\")), mdx(\"div\", {\n    id: \"Lesson5GenreSelectionCollection3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the body, we create a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ScrollView\"), \" , and inside its content block we pass \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LazyHGrid\"), \" containing the array of genres looped over with the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ForEach\"), \" view. \")), mdx(\"div\", {\n    id: \"Lesson5GenreSelectionCollection4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ForEach\"), \" view, we return a genre toggle instance create and returned by the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createGenreToggle\"), \". \"), mdx(\"p\", null, \"Then, In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createGenreToggle\"), \" function, we remove the toggled genre if not selected otherwise we add it to the dictionary.\")), mdx(\"div\", {\n    id: \"Lesson5GenreSelectionHeader1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Genre Selection Header\"), mdx(\"p\", null, \"This one is a component as well, so create a swift file inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Components\")), \" folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreSelectionHeader\"))), mdx(\"p\", null, \"Inside a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \", we have a large title and a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"BodyText\"), \", the latter is an element we've created earlier. We set the alignment of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"leading\"), \" in order to align its children on the left.\")), mdx(\"div\", {\n    id: \"Lesson5GenreSelectionContent1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Genre Selection Content\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Components\")), \" folder, add another swiftUI file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreSelectionContent.\"))), mdx(\"p\", null, \"This component will be composed of other genre components and elements we have created so far. \")), mdx(\"div\", {\n    id: \"Lesson5GenreSelectionContent2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"genres\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"selectedGenres\"), \" will be passed to the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreSelectionCollection\"), \" . The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onContinue\"), \" closure will send selected genres to the calling view.\")), mdx(\"div\", {\n    id: \"Lesson5GenreSelectionContent3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We use a vertically scrolling view this time, and inside it we add a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" that contains the 2 components and the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GetStartedButton element\")), \" we have created earlier. \")), mdx(\"div\", {\n    id: \"Lesson5GenreSelectionContent4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We will only show the button when the user has selected more than 3 genres.\"), mdx(\"p\", null, \"Inside the button's action, we convert \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"selectedGenres\"), \" dictionary values to array by looping through them and appending each one of them to the new genres array created inside the button's action, then we call \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onConitnue\"), \" passing in the genres\")), mdx(\"div\", {\n    id: \"Lesson5GenreSelectionScreen1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Genre Selection Screen\"), mdx(\"p\", null, \"As its name implies, this the screen that will be presented to the user, so we will add it directly under the  \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreSelection\")), \" folder.\")), mdx(\"div\", {\n    id: \"Lesson5GenreSelectionScreen2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The only thing we need here is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"store\"), \" , we will use it to get the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"allGenres\"), \" state as well as dispatching actions to parse the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"genres.json\")), \" file.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Make sure to inject the store in the preview as well for your previews to work.\"))), mdx(\"div\", {\n    id: \"Lesson5GenreSelectionScreen3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"store\"), \", we start observing the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"apiState\"), \" 's \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"allGenres\"), \" property for new changes. And because the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"allGenres\"), \" is a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Result\"), \", we switch on it.\")), mdx(\"div\", {\n    id: \"Lesson5GenreSelectionScreen4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"loading\"), \" case, we show \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreSelectionContent\"), \" , but \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"redacted\"), \" passing in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"listPlaceholders\"), \" . \")), mdx(\"div\", {\n    id: \"Lesson5GenreSelectionScreen5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"success\"), \" case, we return the same \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreSelectionContent\"), \" passing in the associated data, we still need to implement the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onContinue\"), \" closure.\"), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"failure\"), \" case, we are showing a simple \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" for now, but we will  create an appropriate view for the failure state later on.\"), mdx(\"p\", null, \"With that done, let's move on to the next lesson which is lesson number 6. Happy Coding!\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#project-layout","title":"Project layout"},{"url":"#color-sets-and-color-extension","title":"Color sets and Color extension"},{"url":"#accent-button-style","title":"Accent button style","items":[{"url":"#color","title":"Color+"}]},{"url":"#button-toggle-style","title":"Button Toggle Style"},{"url":"#bodytext","title":"BodyText"},{"url":"#get-started-button","title":"Get started button","items":[{"url":"#previews","title":"Previews"}]},{"url":"#genre-toggle","title":"Genre Toggle"},{"url":"#genre-selection-collection","title":"Genre Selection Collection"},{"url":"#genre-selection-header","title":"Genre Selection Header"},{"url":"#genre-selection-content","title":"Genre Selection Content"},{"url":"#genre-selection-screen","title":"Genre Selection Screen"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h2\", null, \"Project layout\"), mdx(\"p\", null, \"Hey there, welcome to lesson 5. How are you enjoying the course so far? Hope you like it. We have done a ton of stuff over the passed four lessons. In this one, we will create our first screen. So buckle up and let's go!\"), mdx(\"p\", null, \"Organising a project and naming files are those things in programming that seem harmless and simple until you try them. It fantastic how my brain can work even when I am asleep, but only stops when it's time name files, lol!... So, in this section I will show you how  we will organise our code. Of course, project layout is subjective, I'll just show what works for me, you are free to modify it however you like. \"), mdx(\"p\", null, \"We have already created folders and files, and that's all good. The project structure I want to show you will mainly be for UI.  Here is the layout: \"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611827974/StreamCast/cwh5akihfwhbcr46pu3t.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611827974/StreamCast/cwh5akihfwhbcr46pu3t.png\"\n  }))), mdx(\"p\", null, \"The main thing to note in the above image is the 3 folders ( \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"*Styles\"), \", \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Elements, Components\"), \" )\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"*.\"), \" \"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Styles\")), \" will contain all custom styles we will create in the project\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Elements\")), \" will contain simple views, mostly one piece of UI, for example a custom \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Button\"), \" or a styled \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Text\"), \", and so on.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Components\")), \"  are a big chunk of UI with which  may construct a an entire screen when combined.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"We may also need a \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Modifiers\"), \" folder down the line, but we are not there yet.\")), mdx(\"p\", null, \"The \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens\")), \"  are what their implies, they will contain various screen that will compose the entire app.\"), mdx(\"h2\", null, \"Color sets and Color extension\"), mdx(\"p\", null, \"Before working on the UI, we need colors, so let's add them. \"), mdx(\"p\", null, \"Here is a list of colors that we will use:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"background\")), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Dark: #000000\")), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Light: #FFFFFF\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"overlay\")), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Dark: #1C1C1E\")), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Light: #F2F2F7\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"pop\"))), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Dark: #F0E416\")), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Light: #F04667\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"shadow\"), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Dark: #000000\")), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Light: seperatorColor\")))), mdx(\"p\", null, \"If you do not know how to add colors in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"asset catalogue\")), \", let's add the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"shadow\")), \" color together. \"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Assets.xcassets,\")), \" tap the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"*\\\"+\\\"\"), \" button* and choose  \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Color Set.\"))), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"While selecting the color set you have just added,  click the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"attributes inspector\")), \" on the right-hand side and change the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"appearances\")), \" value to \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Any, Dark.\")), \" The \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Any Appearance\")), \" will be used for the  light theme and the other one for dark theme.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Now select the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Any Appearance\"), \",\"), \" and you should see more options pop up in the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"inspectors.\")), \" For this color set, the light color is \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"seperatorColor\"), \" which a system color, so we do not need to add color manually, just use the \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"content\"), \" dropdown in the C\", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"olor\"), \" section and choose \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"seperatorColor\")), \" in the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"iOS System Colors\")), \" section.\")), mdx(\"h2\", null, \"Accent button style\"), mdx(\"p\", null, \"This style will be shared by multiple buttons in different screens, so let's put it in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI/Styles.\")), \" \"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"AccentButtonStyle.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct AccentButtonStyle: ButtonStyle {\\n\\n    var verticalPadding: CGFloat\\n\\n    func makeBody(configuration: Self.Configuration) -> some View {\\n\\n        let horizontalPadding = verticalPadding * 2\\n\\n        return configuration.label\\n            .padding(.horizontal, horizontalPadding)\\n            .padding(.vertical, verticalPadding)\\n            .foregroundColor( Color.background)\\n            .background(Color.accentColor)\\n            .cornerRadius(15)\\n            .scaleEffect(configuration.isPressed ? 0.9 : 1.0)\\n    }\\n\\n}\\n\")), mdx(\"p\", null, \"In swiftUI, we can style buttons using custom implementations \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ButtonStyle\"), \" protocol. Here is what we are doing here:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We will only allow the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"verticalPadding\"), \" to be customisable, the horizontal padding will be twice as big the vertical one.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ButtonStyle\"), \" protocol has a required method that returns the body of the button which is a swiftUI View.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The configuration has 2 properties, the first one is the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"label\"), \" which is what the button displays and the second is a boolean property that indicated whether the button is currently pressed or not.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We give a vertical and horizontal paddings to the label.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then , we proceed to adding the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"foregroundColor\"), \" and the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"backgroundColor\"), \" using colors that we defined in the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Color+\"), \" extension file.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Last, we give it a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"cornerRadius\"), \" and a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"scaleEffect\"), \" which is dynamic based on whether the button is  pressed  or not.\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Color+.swift\"))), mdx(\"p\", null, \"Create this file in \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Extensions\")), \"  folder, and just check the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"iOS\")), \" and \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"macOS\")), \" targets\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nextension Color {\\n    static let pop = Color(\\\"pop\\\")\\n    static let background = Color(\\\"background\\\")\\n    static let overlay = Color(\\\"overlay\\\")\\n    static let text = Color(\\\"text\\\")\\n    static let shadow = Color(\\\"shadow\\\")\\n    #if os(iOS)\\n    static let body = Color(UIColor.secondaryLabel)\\n    static let border = Color(UIColor.separator)\\n    #elseif os(macOS)\\n    static let body = Color(NSColor.secondaryLabelColor)\\n    static let border = Color(NSColor.separatorColor)\\n    #endif\\n}\\n\")), mdx(\"h2\", null, \"Button Toggle Style\"), mdx(\"p\", null, \"This style will be used with a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Toggle\"), \" . We want to have a button that can be toggled on and off on demand, likely for us the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Toggle\"), \"  element combined with a custom style in swiftUI is very powerful, so we will use the 2 to create the type of button I have just talked about.  Inside the same folder, add another swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ButtonToggleStyle.\"))), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ButtonToggleStyle.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct ButtonToggleStyle: ToggleStyle {\\n\\n    var cornerRadius: CGFloat\\n\\n    func makeBody(configuration: Configuration) -> some View {\\n        configuration.label\\n            .font(.headline)\\n            .padding()\\n            .foregroundColor(configuration.isOn ? Color.accentColor : Color.gray)\\n            .frame(maxWidth: .infinity)\\n            .background(configuration.isOn ? Color.accentColor.opacity(0.2) : Color.overlay)\\n            .cornerRadius(cornerRadius)\\n            .onTapGesture {\\n                configuration.isOn.toggle()\\n            }\\n    }\\n}\\n\")), mdx(\"p\", null, \"Just like the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ButtonStyle\"), \", the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ToggleStyle\"), \" protocol also has a label which plays the same role as the button's, but instead of having a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isPressed\"), \" property, it's \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isOn\"), \".\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We want to make the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"cornerRadius\"), \" customisable, so  we expose to the calling view.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then set almost similar styles as the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ButtonStyle\"), \".\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The only thing that differs from the button is the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onTapGesture\"), \" which is needed in order to toggle the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"isOn\"), \" property.\")), mdx(\"h2\", null, \"BodyText\"), mdx(\"p\", null, \"This is a simple help text view that only has a \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"body\"), \" font and color. This will also be used by other views in the project, let's put it in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ShareUI/Elements.\"))), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"BodyText.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct BodyText: View {\\n\\n    var value: String\\n\\n    var body: some View {\\n        Text(value)\\n            .font(.body)\\n            .foregroundColor(.body)\\n    }\\n}\\n\\nstruct BodyText_Previews: PreviewProvider {\\n    static var previews: some View {\\n        BodyText(value: \\\"Hello world\\\")\\n    }\\n}\\n\")), mdx(\"p\", null, \"With that,  we are done with  the shared uis in this particular lesson. Let's create our first screen.\"), mdx(\"h2\", null, \"Get started button\"), mdx(\"p\", null, \"The get started button will utilise the button style we have just created. Create a folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens,\")), \" inside it add another one named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreSelection,\")), \" and in that one you will create those 3 folders we've talked about earlier (\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"*Styles\"), \", \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Elements, Components)*\"), \".  This getting started button will only be used  on the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Genre Selection Screen\")), \"  that's why it's inside it unclosing folder. So create a swiftUI file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GetStartedButton\")), \" inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Elements\")), \" folder.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GetStartedButton.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct GetStartedButton: View {\\n\\n    var action = {}\\n\\n    var body: some View {\\n        Button(action: action, label: {\\n            Text(\\\"Get started\\\")\\n        }).buttonStyle(AccentButtonStyle(verticalPadding: 15))\\n    }\\n}\\n\\nstruct GetStartedButton_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            GetStartedButton()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            GetStartedButton()\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"The main thing to note here is how we set the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AccentButtonStyle\"), \", it is passed to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"buttonStyle\"), \" modifer. The other thing is the action property declared above, it's a closure, and as you can see we pass it in the button's action as a normal variable because it has the same method signature as the button's action. \"), mdx(\"p\", null, \"Previews\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611828191/StreamCast/i9hlatqfbs8mgwyo4lat.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611828191/StreamCast/i9hlatqfbs8mgwyo4lat.png\"\n  }))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611828191/StreamCast/ycc3oyhu2brxeinoxssk.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611828191/StreamCast/ycc3oyhu2brxeinoxssk.png\"\n  }))), mdx(\"h2\", null, \"Genre Toggle\"), mdx(\"p\", null, \"This one will use the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ButtonToggleStyle\"), \" in the same exact way we used \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AccentButtonStyle\"), \". So create a new swiftUI file inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Elements.\")), \" Again, we are putting these 2 inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Elements\")), \" folder because they are simple part of a bigger component.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreToggle.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct GenreToggle: View {\\n\\n    var genre: GenreViewModel\\n    var onToggle: ((Bool) -> Void) = {_ in }\\n\\n    @State private var isActive = false\\n\\n    var body: some View {\\n        Toggle(isOn: $isActive, label: {\\n            Text(genre.name)\\n        }).toggleStyle(ButtonToggleStyle(cornerRadius: 30))\\n        .onChange(of: isActive, perform: { isActive in\\n            onToggle(isActive)\\n        })\\n    }\\n}\\n\\nstruct GenreToggle_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            GenreToggle(genre: GenreViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            GenreToggle(genre: GenreViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"There's a lot going on here, so let's break it down:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We declare a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"genre\"), \" view model property\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onToggle\"), \" is a closure that takes in a bool and returns nothing\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"isActive\"), \" is used to update based on the current state of \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"isOn\"), \" binding property of the toggle.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Inside the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"body\"), \", we create the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Toggle\"), \" , its label being a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Text\"), \" displaying the genre name.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We set the style using the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"toggleStyle\"), \" modifier.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onChange\"), \" modiifer will listen to \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"isActive\"), \" state, and run the action block whenever it changes that's where we call the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onToggle\"), \" callback with the value of \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"isActive\"), \".\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Previews\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611828515/StreamCast/gjxs2qrdptg0hgosz9ra.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611828515/StreamCast/gjxs2qrdptg0hgosz9ra.png\"\n  }))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611828515/StreamCast/qzqj1t5j0y0y1hkzbdiz.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611828515/StreamCast/qzqj1t5j0y0y1hkzbdiz.png\"\n  }))), mdx(\"h2\", null, \"Genre Selection Collection\"), mdx(\"p\", null, \"This will contain a horizontal scrollview containing all the genres from which the user can choose his or her preferred ones. This one is a component, so in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Components\")), \" folder, add another swiftUI file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreSelectionCollection.\"))), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreSelectionCollection.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct GenreSelectionCollection: View {\\n\\n    var genres: [GenreViewModel]\\n    @Binding var selectedGenres: [String: GenreViewModel]\\n\\n        private let gridItems = Array(repeating: GridItem.init(.adaptive(minimum: 40), spacing: 20), count: 1)\\n\\n    var body: some View {\\n        ScrollView(.horizontal, showsIndicators: false) {\\n            LazyHGrid(rows: gridItems, alignment: .center, spacing: 10) {\\n                ForEach(genres) { genre in\\n                    createGenreToggle(genre)\\n                }\\n            }.padding()\\n        }.frame(height: 340, alignment: .center)\\n    }\\n\\n    fileprivate func createGenreToggle(_ genre: GenreViewModel) -> GenreToggle {\\n        return GenreToggle(genre: genre) { isSelected in\\n            if isSelected {\\n                selectedGenres[genre.name] = genre\\n            } else {\\n                selectedGenres.removeValue(forKey: genre.name)\\n            }\\n        }\\n    }\\n}\\n\\nstruct GenreSelectionCollection_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            GenreSelectionCollection(genres: GenreViewModel.listPlaceholders, selectedGenres: .constant([:]))\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            GenreSelectionCollection(genres: GenreViewModel.listPlaceholders, selectedGenres: .constant([:]))\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"Let's break down this one:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We have a genres array property that we will use to populate the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ScrollView\"), \". The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"selectedGenres\"), \" is a dictionary which make it easier and efficient to add and remove elements from it even if the difference in performance is negligable.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the body, we create a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ScrollView\"), \" , and inside its content block we pass \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"LazyHGrid\"), \" containing the array of genres looped over with the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ForEach\"), \" view. Inside it we create a genre toggle instance. \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"createGenreToggle\"), \" function, we remove the toggled genre if not selected otherwise we add it to the dictionary.\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Previews\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611828822/StreamCast/vh9qa2zr490au8uqp5ez.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611828822/StreamCast/vh9qa2zr490au8uqp5ez.png\"\n  }))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611828822/StreamCast/xm8uivdsxw5zdn5l72sh.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611828822/StreamCast/xm8uivdsxw5zdn5l72sh.png\"\n  }))), mdx(\"h2\", null, \"Genre Selection Header\"), mdx(\"p\", null, \"This one is a component as well, so create a swift file inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Components\")), \" folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreSelectionHeader\"))), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreSelectionHeader.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct GenreSelectionHeader: View {\\n    var body: some View {\\n        VStack(alignment: .leading, spacing: 20) {\\n            Text(\\\"Genres\\\")\\n                .font(.largeTitle)\\n                .bold()\\n\\n            BodyText(value: \\\"To get started please choose at least 3 categories that interest you so that we provide you with what like from the start. Go ahead!\\\")\\n        }.padding()\\n    }\\n}\\n\\nstruct GenreSelectionHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            GenreSelectionHeader()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            GenreSelectionHeader()\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"Let's go over what that piece of does.\"), mdx(\"p\", null, \"Inside a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \", we have a large title and a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"BodyText\"), \", the latter is an element we've created earlier. We set the alignment of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"leading\"), \" in order to align its children on the left.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Previews\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611829119/StreamCast/k9kc2xpfnsgngoszlzro.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611829119/StreamCast/k9kc2xpfnsgngoszlzro.png\"\n  }))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611829119/StreamCast/xvtwkivla6qevfr1tksp.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611829119/StreamCast/xvtwkivla6qevfr1tksp.png\"\n  }))), mdx(\"h2\", null, \"Genre Selection Content\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Components\")), \" folder, add another swiftUI file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreSelectionContent.\"))), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreSelectionContent.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct GenreSelectionContent: View {\\n\\n    var genres: [GenreViewModel]\\n    var onContinue: (([GenreViewModel]) -> Void) = {_ in }\\n\\n    @State private var selectedGenres: [String: GenreViewModel] = [:]\\n\\n    var body: some View {\\n\\n        ScrollView(showsIndicators: false) {\\n            VStack(spacing: 30) {\\n                GenreSelectionHeader()\\n                GenreSelectionCollection(genres: genres, selectedGenres: $selectedGenres)\\n\\n                if selectedGenres.count > 2 {\\n                    GetStartedButton(action: {\\n                        var genres = [GenreViewModel]()\\n                                selectedGenres.values.forEach { genres.append($0) }\\n                                  onContinue(genres)\\n                    })\\n                }\\n            }.padding(.vertical, 40)\\n        }.background(Color.background)\\n    }\\n}\\n\\nstruct GenreSelectionContent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            GenreSelectionContent(genres: GenreViewModel.listPlaceholders)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            GenreSelectionContent(genres: GenreViewModel.listPlaceholders)\\n                .preferredColorScheme(.light)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"This component is composed of other components and elements we have created. So let's go over what we have just done:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"genres\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"selectedGenres\"), \" will be passed to the  \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"GenreSelectionCollection\"), \" . The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onContinue\"), \" closure will send selected genres to the calling view.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We use a vartically scrolling view this time, and inside it we add a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"VStack\"), \" that contains the 2 components  \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"**\"), \"and the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GetStartedButton element\")), \" we have created  earlier. However, we will only show the button when the user has selected more than 3 genres.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Inside the button's action, we convert \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"selectedGenres\"), \" dictionary values to array by looping through them and appending each one of them to the new genres array created inside the button's action, then we call \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onConitnue\"), \" passing in the genres\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Previews\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611829523/StreamCast/j4rox5brvdh7qsakrkrn.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611829523/StreamCast/j4rox5brvdh7qsakrkrn.png\"\n  }))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611829523/StreamCast/v5hom6ssy3jy8z3eb7zm.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611829523/StreamCast/v5hom6ssy3jy8z3eb7zm.png\"\n  }))), mdx(\"h2\", null, \"Genre Selection Screen\"), mdx(\"p\", null, \"As its name implies, this the screen that will be presented to the user, so we will add it directly under the  \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreSelection\")), \" folder.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct GenreSelectionScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    var body: some View {\\n        switch store.apiState.allGenres {\\n        case .loading:\\n            GenreSelectionContent(genres: GenreViewModel.listPlaceholders) { _ in\\n            }.onAppear(perform: {\\n                store.dispatch(.api(.fetchGenres(\\\"genres\\\")))\\n            }).redacted(reason: .placeholder)\\n        case .success(let data):\\n            GenreSelectionContent(genres: data) { _ in\\n\\n            }\\n        case .failure:\\n            Text(\\\"Failed to Load genres...\\\")\\n        }\\n    }\\n}\\n\\nstruct GenreSelectionScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        GenreSelectionScreen().environmentObject(Store())\\n    }\\n}\\n\")), mdx(\"p\", null, \"Let's go over the code now:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The only thing we need here is the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"store\"), \" , we will use it to get the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"allGenres\"), \" state as well as dispatching actions to parse the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"genres.json\")), \" file.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the body, we switch on the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"allGenres\"), \" state result, when it's \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"loading\"), \" we show \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"GenreSelectionContent\"), \" , but \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"redacted\"), \" passing in the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"listPlaceholders\"), \" . \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"success\"), \" case, we return the same \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"GenreSelectionContent\"), \" passing in the assiociated data, we still need to implement the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onContinue\"), \" closure.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"failure\"), \" case, we are showing a simple \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Text\"), \" for now, but we will  create an appropriate view for the failure state later on.\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Previews\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611829987/StreamCast/do0sftmypfbu9i0thuom.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611829987/StreamCast/do0sftmypfbu9i0thuom.png\"\n  }))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611829987/StreamCast/kigz47xhab1zvkhmp77u.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611829987/StreamCast/kigz47xhab1zvkhmp77u.png\"\n  }))), mdx(\"p\", null, \"With that done, let's move on to the next lesson which is lesson number 6. Happy Coding!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#project-layout","title":"Project layout"},{"url":"#color-sets-and-color-extension","title":"Color sets and Color extension"},{"url":"#accent-button-style","title":"Accent button style"},{"url":"#button-toggle-style","title":"Button Toggle Style"},{"url":"#bodytext","title":"BodyText"},{"url":"#get-started-button","title":"Get started button"},{"url":"#genre-toggle","title":"Genre Toggle"},{"url":"#genre-selection-collection","title":"Genre Selection Collection"},{"url":"#genre-selection-header","title":"Genre Selection Header"},{"url":"#genre-selection-content","title":"Genre Selection Content"},{"url":"#genre-selection-screen","title":"Genre Selection Screen"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-02-01T09:27:19.708Z","isNewLayout":true,"previews":null,"slug":"lesson-6-saving-genres-in-coredata","title":"Lesson #6: Saving genres in CoreData","updatedDate":"2021-01-30T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZOODbXZx4y79fOKYQBYQh0KPxVSBJdyybIV","type":"zip","id":"8b7a788a-e5ad-54f6-8b9a-96edc0c8bab5","name":"Source code"}],"allChapters":["Lesson6Introduction:Introduction","Lesson6GenreManagedObject1:Genre managed object model","Lesson6GenreManagedObject2: Creating an Entity and Its Properties","Lesson6UpdateGenreViewModel1: Updating the genre view model","Lesson6UpdatePersistenceController1: Updating PersistenceController","Lesson6CoredataServiceProtocol1: Coredata service protocol","Lesson6CoredataService1: Coredata service","Lesson6UpdateAppEnvironment1: Updating AppEnvironment","Lesson6UpdateAction1: Updating Action","Lesson6UpdateAppState1: Updating AppState","Lesson6CoredataReducer1: Coredata Reducer","Lesson6UpdateUpdateGSS1: Saving selected genres"],"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson6GenreManagedObject2\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611997925/StreamCast/zatcywokxjznhwomby1y.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611997925/StreamCast/zatcywokxjznhwomby1y.png\"\n  })))), mdx(\"div\", {\n    id: \"Lesson6GenreManagedObject3\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611997925/StreamCast/zsdru86xrugulpt0opu7.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611997925/StreamCast/zsdru86xrugulpt0opu7.png\"\n  })))), mdx(\"div\", {\n    id: \"Lesson6GenreManagedObject4\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611998182/StreamCast/wk5naqvtbz3mlexgox2t.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611998182/StreamCast/wk5naqvtbz3mlexgox2t.png\"\n  })))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson6UpdateGenreViewModel1\\n// $$liquidcoderFilename-GenreViewModel\\nimport Foundation\\n\\nstruct GenreViewModel: Identifiable, Hashable {\\n    var id: Int\\n    var name: String\\n\\n    static let placeholder = createPlaceholder(id: 1)\\n\\n    static var listPlaceholders: [GenreViewModel] {\\n        (0..<15).map {\\n            createPlaceholder(id: $0)\\n        }\\n    }\\n\\n    private static func createPlaceholder(id: Int) -> GenreViewModel {\\n        GenreViewModel( Genre(id: id, name: \\\"Technology\\\") )\\n    }\\n\\n    init(_ genre: Genre) {\\n        self.id = genre.id\\n        self.name = genre.name\\n    }\\n\\n    // highlight-start\\n    init(genre managedObject: GenreManagedObject) {\\n        self.id = Int(managedObject.id)\\n        self.name = managedObject.name ?? \\\"\\\"\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\n// $$liquidcoderID-Lesson6UpdatePersistenceController1\\n// $$liquidcoderFilename-PersistenceController\\n\\nimport CoreData\\n\\nstruct PersistenceController {\\n    static let shared = PersistenceController()\\n    let container: NSPersistentCloudKitContainer\\n\\n    init(inMemory: Bool = false) {\\n        container = NSPersistentCloudKitContainer(name: \\\"StreamCast\\\")\\n        if inMemory {\\n            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: \\\"/dev/null\\\")\\n        }\\n        container.loadPersistentStores(completionHandler: { (_, error) in\\n            if let error = error as NSError? {\\n                fatalError(\\\"Unresolved error \\\\(error), \\\\(error.userInfo)\\\")\\n            }\\n        })\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson6CoredataServiceProtocol2\\n// $$liquidcoderFilename-CoredataServiceProtocol\\n\\nimport Foundation\\n\\nprotocol CoredataServiceProtocol {\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool\\n    func fetchGenres() -> [GenreViewModel]\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson6CoredataService1\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\n// highlight-start\\nimport CoreData\\n// highlight-end\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n    // highlight-start\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool {\\n        return false\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    func fetchGenres() -> [GenreViewModel] {\\n\\n        return []\\n    }\\n    // highlight-end\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson6CoredataService2\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n    // highlight-start\\n    private var context: NSManagedObjectContext {\\n        return PersistenceController.shared.container.viewContext\\n    }\\n    // highlight-end\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool {\\n        return false\\n    }\\n\\n    func fetchGenres() -> [GenreViewModel] {\\n\\n        return []\\n    }\\n\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson6CoredataService3\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n    private var context: NSManagedObjectContext {\\n        return PersistenceController.shared.container.viewContext\\n    }\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool {\\n        // highlight-start\\n        genres.forEach {\\n            let newGenre = GenreManagedObject(context: context)\\n            newGenre.id = Int64($0.id)\\n            newGenre.name = $0.name\\n        }\\n        return saveContext()\\n        // highlight-end\\n    }\\n\\n    func fetchGenres() -> [GenreViewModel] {\\n\\n        return []\\n    }\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson6CoredataService4\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n    private var context: NSManagedObjectContext {\\n        return PersistenceController.shared.container.viewContext\\n    }\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool {\\n        genres.forEach {\\n            let newGenre = GenreManagedObject(context: context)\\n            newGenre.id = Int64($0.id)\\n            newGenre.name = $0.name\\n        }\\n        return saveContext()\\n    }\\n\\n    func fetchGenres() -> [GenreViewModel] {\\n        // highlight-start\\n        let request: NSFetchRequest<GenreManagedObject> = GenreManagedObject.fetchRequest()\\n\\n        do {\\n            let results = try context.fetch(request)\\n            return results.map { GenreViewModel(genre: $0) }\\n        } catch let error {\\n            Log.error(error)\\n            let nsError = error as NSError\\n            fatalError(\\\"Unresolved error \\\\(nsError), \\\\(nsError.userInfo)\\\")\\n        }\\n        // highlight-end\\n    }\\n\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson6UpdateAppEnvironment1\\n// $$liquidcoderFilename-AppEnvironment\\n\\nimport Foundation\\n\\nstruct AppEnvironment {\\n    let decoder: JSONDecoder\\n    let api: ApiServiceProtocol\\n    // highlight-start\\n    let coredata: CoredataServiceProtocol\\n    // highlight-end\\n\\n    init(decoder: JSONDecoder = .init(),\\n         api: ApiServiceProtocol = ApiService(),\\n         // highlight-start\\n         coredata: CoredataServiceProtocol = CoredataService()) {\\n        // highlight-end\\n        self.api = api\\n        self.decoder = decoder\\n        // highlight-start\\n        self.coredata = coredata\\n        // highlight-end\\n    }\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson6UpdateAction1\\n// $$liquidcoderFilename-Action\\n\\nimport Foundation\\n\\nenum Action {\\n    enum Api {\\n        case fetchPodcasts(_ term: String, limit: Int = 10)\\n        case fetchGenres(_ filename: String)\\n    }\\n\\n    // highlight-start\\n    enum Coredata {\\n        case fetchGenres\\n        case saveGenres(_ genres: [GenreViewModel])\\n    }\\n    // highlight-end\\n\\n    case api(_ action: Api)\\n    // highlight-start\\n    case coredata(_ action: Coredata)\\n    // highlight-end\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson6UpdateAppState1\\n// $$liquidcoderFilename-AppState\\n\\nimport Foundation\\nimport SwiftUI\\n\\nenum AppState {\\n\\n    enum Result<T: Equatable>: Equatable {\\n\\n        static func == (lhs: AppState.Result<T>, rhs: AppState.Result<T>) -> Bool {\\n            ...\\n        }\\n\\n        struct Api {\\n            var preferredPodcasts: [String: Result<[PodcastViewModel]>] = [:]\\n            var allGenres: Result<[GenreViewModel]> = .loading\\n        }\\n\\n        // highlight-start\\n        struct Coredata {\\n            var savedGenres: [GenreViewModel] = []\\n        }\\n        // highlight-end\\n    }\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson6UpdateStore1\\n// $$liquidcoderFilename-Store\\n\\nimport Foundation\\nimport Combine\\n\\nclass Store: ObservableObject {\\n\\n    @Published private(set) var apiState = AppState.Api()\\n\\n    private var subscriptions = Set<AnyCancellable>()\\n\\n    private let environment: AppEnvironment\\n\\n    init(environment: AppEnvironment = AppEnvironment() ) {\\n        self.environment = environment\\n    }\\n\\n    func dispatch(_ action: Action) {\\n        switch action {\\n        case .api(let action):\\n            ApiReducer.execute(action: action, state: apiState, environment: environment) { [weak self] state in\\n                self?.apiState = state\\n            }.store(in: &subscriptions)\\n        // highlight-start\\n        case .coredata(let action):\\n            break\\n        // highlight-end\\n        }\\n    }\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson6CoredataReducer1\\n// $$liquidcoderFilename-CoredataReducer\\n\\nimport Foundation\\n\\nstruct CoredataReducer {\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson6CoredataReducer2\\n// $$liquidcoderFilename-CoredataReducer\\n\\nimport Foundation\\n\\nstruct CoredataReducer {\\n\\n    // highlight-start\\n    static func execute(action: Action.Coredata, state: inout AppState.Coredata, environment: AppEnvironment) {\\n\\n    }\\n    // highlight-end\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson6CoredataReducer3\\n// $$liquidcoderFilename-CoredataReducer\\n\\nimport Foundation\\n\\nstruct CoredataReducer {\\n\\n    static func execute(action: Action.Coredata, state: inout AppState.Coredata, environment: AppEnvironment) {\\n\\n        // highlight-start\\n        switch action {\\n\\n        case .fetchGenres:\\n            state.savedGenres = environment.coredata.fetchGenres()\\n        case .saveGenres(let genres):\\n            _ = environment.coredata.saveGenres(genres)\\n        }\\n        // highlight-end\\n    }\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson6UpdateStore2\\n// $$liquidcoderFilename-Store\\n\\nimport Foundation\\nimport Combine\\n\\nclass Store: ObservableObject {\\n\\n    @Published private(set) var apiState = AppState.Api()\\n    // highlight-start\\n    @Published private(set) var coredataState = AppState.Coredata()\\n    // highlight-end\\n\\n    private var subscriptions = Set<AnyCancellable>()\\n\\n    private let environment: AppEnvironment\\n\\n    init(environment: AppEnvironment = AppEnvironment() ) {\\n        self.environment = environment\\n    }\\n\\n    func dispatch(_ action: Action) {\\n        switch action {\\n        case .api(let action):\\n            ...\\n        case .coredata(let action):\\n            // highlight-start\\n            CoredataReducer.execute(action: action, state: &coredataState, environment: environment)\\n        // highlight-end\\n        }\\n    }\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson6UpdateUpdateGSS1\\n// $$liquidcoderFilename-GenreSelectionScreen\\n\\nimport SwiftUI\\n\\nstruct GenreSelectionScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    var body: some View {\\n        switch store.apiState.allGenres {\\n        case .loading:\\n            ...\\n        case .success(let genres):\\n            GenreSelectionContent(genres: genres, onContinue: {  selectedGenres in\\n                // highlight-start\\n                store.dispatch(.coredata(.saveGenres(selectedGenres)))\\n                // highlight-end\\n            })\\n        case .failure:\\n            Text(\\\"Oops! something went kaboom!!!\\\")\\n        }\\n    }\\n}\\n...\\n\\n\")), mdx(\"div\", {\n    id: \"Lesson6AddPersistenceToWOS\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612000091/StreamCast/iwjtndihtf2h9lmkanwt.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612000091/StreamCast/iwjtndihtf2h9lmkanwt.png\"\n  })))));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson6Introduction-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hello guys, where were we? Oh yeah, we have just created our first screen, but it's doing nothing for now. The goal of the genre selection screen is to allow users to choose their preferred genres of podcast and save them in core data, but we haven't implemented coredata yet, that is what we are doing in this lesson, let's get into it!\")), mdx(\"div\", {\n    id: \"Lesson6GenreManagedObject1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Genre managed object model\"), mdx(\"p\", null, \"We will have 3 managed object models by the end of the course, but for now , we will create only one, and will add the remaining later.  \"), mdx(\"p\", null, \"The following section is taken directly from the apple documentation archive, it explains how to create entities and  properties well enough that I don't think there is any need to change it whatsoever.  I also recommend you read more on  this page \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/KeyConcepts.html\"\n  }), \"https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/KeyConcepts.html\"))), mdx(\"div\", {\n    id: \"Lesson6GenreManagedObject0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Creating an Entity and Its Properties\"), mdx(\"p\", null, \"Inside your project, look for a file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"StreamCast.xcdatamodeld\")), \". This is the file that we will use to create schemas which are \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NSManagedObject\"), \" subclasses.\\nselect that file in the navigator area to display the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"CoreData\")), \" model editor.\")), mdx(\"div\", {\n    id: \"Lesson6GenreManagedObject2-Section\",\n    className: \"post-section\"\n  }, mdx(\"h3\", null, mdx(\"strong\", {\n    parentName: \"h3\"\n  }, \"To create an entity\")), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Click Add Entity.\"), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"A new untitled entity appears in the Entities list in the navigator area.\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Select the new untitled entity.\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"In the Entity pane of the Data Model inspector, enter the name, in our case \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreManagedObject ,\")), \" of the entity, and press Return.\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"You need to delete the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Item\")), \" entity.\"))), mdx(\"h3\", null, mdx(\"strong\", {\n    parentName: \"h3\"\n  }, \"To create attributes and relationships for the entity\")), mdx(\"p\", null, \"The \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreManagedObject\")), \" will have an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"id\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"name\"), \" as attributes with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"integer64\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"String\"), \" as data type respectively.  So follow this to add them.\"), mdx(\"p\", null, \"1.With the new entity selected, click the Add button \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"(+)\"), \" at the bottom of the appropriate section.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {}), \"A new untitled attribute or relationship (generically referred to as a\\n property) is added in the Attributes or Relationships section of the \\neditor area.\\n\")), mdx(\"p\", null, \"2.Select the new untitled property.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {}), \"The property settings are displayed in the Relationship pane or Attribute pane of the Data Model inspector.\\n\")), mdx(\"p\", null, \"3.Give the property a name, and press Return.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {}), \"The attribute or relationship information appears in the editor area.\\n\"))), mdx(\"div\", {\n    id: \"Lesson6GenreManagedObject3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"When you are done adding the 2 attributes, we need  to generate a class that will sub-class \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NSManageObject\"), \", and to do that we need to make a small change over to the model inspector.\"), mdx(\"p\", null, \"While the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreManagedObject\"), \" is selected,  on  the model inspector, located to the right , set the module to the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"current project module\"), \" and set the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"codegen\")), \" to \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Manual/None\"), \" .\")), mdx(\"div\", {\n    id: \"Lesson6GenreManagedObject4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"To generate the class file initially:\"), mdx(\"p\", null, \"Choose \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Editor > Create NSManagedObject Subclass\")), \" from the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"menu\"), \" at the top of the screen.\")), mdx(\"div\", {\n    id: \"Lesson6GenreManagedObject5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Check your the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Shared\")), \" data model, then the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreManagedObject\")), \" entity, and choose where to save the files, just save them in the root folder, we will move them in the appropriate folder after and make sure you have checked all the targets except the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"watchOS\"), \" one.  Now create a new folder in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Shared\")), \" one named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Coredata.\")), \" Move the generated files into that folder.\"), mdx(\"p\", null, \"Next, we need another initialiser in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreViewModel\"), \"  to make working with the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreManagedObject\"), \" a little easier.  \")), mdx(\"div\", {\n    id: \"Lesson6UpdateGenreViewModel1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the genre view model\"), mdx(\"p\", null, \"Open the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreViewModel.swift\")), \" file, and add the following inside:\")), mdx(\"div\", {\n    id: \"Lesson6UpdatePersistenceController1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating PersistenceController\"), mdx(\"p\", null, \"There's something wrong in this file, so let's fix that. Remove the entire \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"preview\"), \" declaration. Your file should look like this\"), mdx(\"p\", null, \"After you've done that, move \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PersistenceController\"), \" in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Coredata\")), \" folder.\")), mdx(\"div\", {\n    id: \"Lesson6CoredataServiceProtocol1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Coredata service protocol\"), mdx(\"p\", null, \"We will need a coredata service to manage coredata \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"CRUD\"), \" operations. The service will be managed by the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppEnvironment\"), \" , so we will first need to create a protocol which, like the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiService\"), \", will make it possible to inject any implementation into the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppEnvironment\"), \" . This is dependacy injection in a nutshell .\")), mdx(\"div\", {\n    id: \"Lesson6CoredataServiceProtocol2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Coredata\")), \" folder, add a swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"CoredataServiceProtocol\")), \" containing the following code\"), mdx(\"p\", null, \"We will add more to this file as we go along. As you can see we are only interested in genres for now. So, let's create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CoredataService\"), \" class now.\")), mdx(\"div\", {\n    id: \"Lesson6CoredataService1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Coredata service\"), mdx(\"p\", null, \"In the same \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Coredata\")), \" folder, add a swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"CoredataService\")), \" containing the following code.\"), mdx(\"p\", null, \"First we need to import the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"CoreData package.\"), \" \"), mdx(\"p\", null, \"As you can see, the 2 protocol methods just return false and an empty array respectively, we will fill them shortly. Here is a quick rundown of what we have so far.\")), mdx(\"div\", {\n    id: \"Lesson6CoredataService2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next we declare a context which is a reference to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NSManagedObjectContext\"), \" class. We will use this one to perform \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"CRUD\"), \" operations in \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"CoreData.\"))), mdx(\"div\", {\n    id: \"Lesson6CoredataService3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Here we loop through the genre \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreViewModel\"), \" array, and for each one , we create a new \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreManagedObject\"), \" , then set the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"id\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"name\"), \",  save the context and return whatever is returned from the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"saveContext\"), \" function\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Don't mind the error, we will create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"saveContext\"), \" in a bit.\"))), mdx(\"div\", {\n    id: \"Lesson6CoredataService4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We create a request using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fetchRequest\"), \" function that comes with the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreManagedObject\"), \". We then use that request to fetch genres from the database and convert the managed objects to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreViewModel\"), \" before returning them.\")), mdx(\"div\", {\n    id: \"Lesson6UpdateAppEnvironment1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating AppEnvironment\"), mdx(\"p\", null, \"Open the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"AppEnvironment.swift\")), \" file\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \",\")), \" and add the highlighted lines inside the struct.\")), mdx(\"div\", {\n    id: \"Lesson6UpdateAction1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating Action\"), mdx(\"p\", null, \"Open the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Action.swift\")), \" file\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \",\")), \" and add the highlighted lines inside the struct.\"), mdx(\"p\", null, \"We will have to add actions that the user will perform in order to fetch or save genres. Right now, we only have an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Api\"), \" enum in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Action\"), \" , let's add another one. This one will be for coredata related operations. So in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Action.swift\")), \" file, add the following:\")), mdx(\"div\", {\n    id: \"Lesson6UpdateAppState1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating AppState\"), mdx(\"p\", null, \"Right now, we only have the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Api\"), \" struct in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppState\"), \" enum,  we'll need to add another  struct for \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"coredata\"), \" state. So add the highlighted below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Api\"), \" struct.\")), mdx(\"div\", {\n    id: \"Lesson6UpdateStore1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now you should have an error caused by the new case in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Action\"), \" file. To fix it, add the highlighted to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"dispatch\"), \" method in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Store\"), \" class.\"), mdx(\"p\", null, \"We will implement that case shortly, first  we need a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CoredataReducer\"), \" .  So, this reducer will be similar to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiReducer\"), \" , but will handle coredata related operations.\")), mdx(\"div\", {\n    id: \"Lesson6CoredataReducer1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Coredata Reducer\"), mdx(\"p\", null, \"Create a new swift file in \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Store\")), \" folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"CoredataReducer.\")), \" Then add the following code inside.\")), mdx(\"div\", {\n    id: \"Lesson6CoredataReducer2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The thing to pay attention to is the change in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"action\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" type, so here we are using \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Coredata\"), \" rather than \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Api\"), \".  Also we add a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"inout\"), \" keyword before \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppState.Coredata\"), \" to make the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" mutable inside the function body.\")), mdx(\"div\", {\n    id: \"Lesson6CoredataReducer3-Section\",\n    className: \"post-section\"\n  }, mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"fetchGenres\"), \", we fetch genres from coredata and set them to the state\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"saveGenres\"), \", we saved genres that were passed in ( these are genres that the user selected on the genre selection screen).\"))), mdx(\"div\", {\n    id: \"Lesson6UpdateStore2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Open the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Store.swift\")), \" file again\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \",\")), \" and add the highlighted lines inside the struct.\"), mdx(\"p\", null, \"In the dispatch function, we are passing the required arguments, but the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"coredataState\"), \" is passed by reference with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"&\"), \" pre-pended before it because of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"inout\"), \" keyword in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"execute\"), \" declaration.\")), mdx(\"div\", {\n    id: \"Lesson6UpdateUpdateGSS1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Saving selected genres\"), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreSelectionScreen\"), \" view, add the highlighted line in the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".success\"), \" case.\"), mdx(\"p\", null, \"What we are doing here is dispatching a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"coredata\"), \" action to the store to save selected genres. \")), mdx(\"div\", {\n    id: \"Lesson6AddPersistenceToWOS-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"If you build the app, you will get an error caused by the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Persistence\")), \" not being in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"watchOS Extension\")), \" target. To fix that, select \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Persistence.swift\")), \" and open the file inspector, under \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Target Membership,\")), \" check the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"WatchOS Extension.\"))), mdx(\"p\", null, \"Now, you can build the project \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"(CMD + B)\")), \", and the error should be fixed.\"), mdx(\"p\", null, \"With this guys, we are done with lesson number 6. Let's move on to lesson number 7.\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#genre-managed-object-model","title":"Genre managed object model"},{"url":"#creating-an-entity-and-its-properties","title":"Creating an Entity and Its Properties","items":[{"url":"#to-create-an-entity","title":"To create an entity"},{"url":"#to-create-attributes-and-relationships-for-the-entity","title":"To create attributes and relationships for the entity"}]},{"url":"#updating-the-genre-view-model","title":"Updating the genre view model"},{"url":"#updating-persistencecontroller","title":"Updating PersistenceController"},{"url":"#coredata-service-protocol","title":"Coredata service protocol"},{"url":"#coredata-service","title":"Coredata service"},{"url":"#updating-appenvironment","title":"Updating AppEnvironment"},{"url":"#updating-action","title":"Updating Action"},{"url":"#updating-appstate","title":"Updating AppState"},{"url":"#coredata-reducer","title":"Coredata Reducer"},{"url":"#saving-selected-genres","title":"Saving selected genres"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Hello guys, where were we? Oh yeah, we have just created our first screen, but it's doing nothing for now. The goal of the genre selection screen is to allow users to choose their preferred genres of podcast and save them in core data, but we haven't implemented coredata yet, that is what we are doing in this lesson, let's get into it!\"), mdx(\"h2\", null, \"Genre managed object model\"), mdx(\"p\", null, \"We will have 3 managed object models by the end of the course, but for now , we will create only one, and will add the remaining later.  \"), mdx(\"p\", null, \"The following section is taken directly from the apple documentation archive, it explains how to create entities and  properties well enough that I don't think there is any need to change it whatsoever.  I also recommend you read more on  this page \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/KeyConcepts.html\"\n  }), \"https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/KeyConcepts.html\")), mdx(\"h2\", null, \"Creating an Entity and Its Properties\"), mdx(\"p\", null, \"Inside your project, look for a file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"StreamCast.xcdatamodeld\")), \". This is the file that we will use to create schemas which are \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NSManagedObject\"), \" subclasses.\\nselect that file in the navigator area to display the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"CoreData\")), \" model editor.\"), mdx(\"h3\", null, mdx(\"strong\", {\n    parentName: \"h3\"\n  }, \"To create an entity\")), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Click Add Entity.\"), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"A new untitled entity appears in the Entities list in the navigator area.\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Select the new untitled entity.\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"In the Entity pane of the Data Model inspector, enter the name, in our case \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreManagedObject ,\")), \" of the entity, and press Return.\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"You need to delete the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Item\")), \" entity.\"))), mdx(\"h3\", null, mdx(\"strong\", {\n    parentName: \"h3\"\n  }, \"To create attributes and relationships for the entity\")), mdx(\"p\", null, \"The \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreManagedObject\")), \" will have an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"id\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"name\"), \" as attributes with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"integer64\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"String\"), \" as data type respectively.  So follow this to add them.\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"With the new entity selected, click the Add button \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"(+)\"), \" at the bottom of the appropriate section.\"), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"A new untitled attribute or relationship (generically referred to as a\\nproperty) is added in the Attributes or Relationships section of the\\neditor area.\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Select the new untitled property.\"), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"The property settings are displayed in the Relationship pane or Attribute pane of the Data Model inspector.\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Give the property a name, and press Return.\"), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"The attribute or relationship information appears in the editor area.\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611997925/StreamCast/zatcywokxjznhwomby1y.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611997925/StreamCast/zatcywokxjznhwomby1y.png\"\n  }))), mdx(\"p\", null, \"When you are done adding the 2 attributes, we need  to generate a class that will sub-class \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NSManageObject\"), \", and to do that we need to make a small change over to the model inspector.\"), mdx(\"p\", null, \"While the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreManagedObject\"), \" is selected,  on  the model inspector, located to the right , set the module to the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"current project module\"), \" and set the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"codegen\")), \" to \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Manual/None\"), \" .\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611997925/StreamCast/zsdru86xrugulpt0opu7.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611997925/StreamCast/zsdru86xrugulpt0opu7.png\"\n  }))), mdx(\"p\", null, \"To generate the class file initially:\"), mdx(\"p\", null, \"Choose \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Editor > Create NSManagedObject Subclass\")), \" from the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"menu\"), \" at the top of the screen.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611998182/StreamCast/wk5naqvtbz3mlexgox2t.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1611998182/StreamCast/wk5naqvtbz3mlexgox2t.png\"\n  }))), mdx(\"p\", null, \"Check your the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Shared\")), \" data model, then the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreManagedObject\")), \" entity, and choose where to save the files, just save them in the root folder, we will move them in the appropriate folder after and make sure you have checked all the targets except the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"watchOS\"), \" one.  Now create a new folder in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Shared\")), \" one named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Coredata.\")), \" Move the generated files into that folder.\"), mdx(\"p\", null, \"Next, we need another initialiser in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreViewModel\"), \"  to make working with the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreManagedObject\"), \" a little easier.  \"), mdx(\"h2\", null, \"Updating the genre view model\"), mdx(\"p\", null, \"Open the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreViewModel.swift\")), \" file, and add the following inside:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreViewModel.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"init(genre managedObject: GenreManagedObject) {\\n    self.id = Int(managedObject.id)\\n    self.name = managedObject.name ?? \\\"\\\"\\n}\\n\")), mdx(\"h2\", null, \"Updating PersistenceController\"), mdx(\"p\", null, \"There's something wrong in this file, so let's fix that. Remove the entire \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"preview\"), \" declaration. Your file should look like this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import CoreData\\n\\nstruct PersistenceController {\\n    static let shared = PersistenceController()\\n    let container: NSPersistentCloudKitContainer\\n\\n    init(inMemory: Bool = false) {\\n        container = NSPersistentCloudKitContainer(name: \\\"StreamCast\\\")\\n        if inMemory {\\n            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: \\\"/dev/null\\\")\\n        }\\n        container.loadPersistentStores(completionHandler: { (_, error) in\\n            if let error = error as NSError? {\\n                fatalError(\\\"Unresolved error \\\\(error), \\\\(error.userInfo)\\\")\\n            }\\n        })\\n    }\\n}\\n\")), mdx(\"p\", null, \"After you've done that, move \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PersistenceController\"), \" in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Coredata\")), \" folder.\"), mdx(\"h2\", null, \"Coredata service protocol\"), mdx(\"p\", null, \"We will need a coredata service to manage coredata \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"CRUD\"), \" operations. The service will be managed by the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppEnvironment\"), \" , so we will first need to create a protocol which, like the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiService\"), \", will make it possible to inject any implementation into the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppEnvironment\"), \" . This is dependacy injection in a nutshell .\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Coredata\")), \" folder, add a swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"CoredataServiceProtocol\")), \" containing the following code:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"CoredataServiceProtocol.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\n\\nprotocol CoredataServiceProtocol {\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool\\n    func fetchGenres() -> [GenreViewModel]\\n}\\n\")), mdx(\"p\", null, \"We will add more to this file as we go along. As you can see we are only interested in genres for now. So, let's create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CoredataService\"), \" class now.\"), mdx(\"h2\", null, \"Coredata service\"), mdx(\"p\", null, \"In the same \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Coredata\")), \" folder, add a swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"CoredataService\")), \" containing the following code:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"CoredataService.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n    private var context: NSManagedObjectContext {\\n        return PersistenceController.shared.container.viewContext\\n    }\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool {\\n\\n        return false\\n    }\\n\\n    func fetchGenres() -> [GenreViewModel] {\\n\\n        return []\\n    }\\n\\n    private func saveContext() -> Bool {\\n        do {\\n            if context.hasChanges {\\n                try context.save()\\n            }\\n        } catch {\\n            let nsError = error as NSError\\n            fatalError(\\\"Unresolved error \\\\(nsError), \\\\(nsError.userInfo)\\\")\\n        }\\n\\n        return true\\n    }\\n}\\n\")), mdx(\"p\", null, \"As you can see, the 2 protocol methods just return false and an empty array respectively, we will fill them shortly. Here is a quick rundown of what we have so far.\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"First we need to import the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"CoreData package.\"), \" \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Next we declare a context which is a reference to the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"NSManagedObjectContext\"), \" class. We use this one to perfom \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"CRUD\"), \" operations in \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"CoreData.\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The 2 protocols will be implemented shorly. The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"saveContext\"), \" function checks whether there are changes in the context and saves it.\")), mdx(\"h3\", null, \"Saving genres\"), mdx(\"p\", null, \"Now replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"saveGenres\"), \" function with the following: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"func saveGenres(_ genres: [GenreViewModel]) -> Bool {\\n      genres.forEach {\\n          let newGenre = GenreManagedObject(context: context)\\n          newGenre.id = Int64($0.id)\\n          newGenre.name = $0.name\\n      }\\n      return saveContext()\\n }\\n\")), mdx(\"p\", null, \"Here we loop through the genre \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreViewModel\"), \" array, and for each one , we create a new \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreManagedObject\"), \" , then set the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"id\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"name\"), \",  save the context and return whatever is returned from the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"saveContext\"), \" function\"), mdx(\"h3\", null, \"Fetching genres\"), mdx(\"p\", null, \"Next, replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fetchGenres\"), \" function with the following: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"func fetchGenres() -> [GenreViewModel] {\\n    let request: NSFetchRequest<GenreManagedObject> = GenreManagedObject.fetchRequest()\\n\\n    do {\\n        let results = try context.fetch(request)\\n        return results.map { GenreViewModel(genre: $0) }\\n    } catch let error {\\n        Log.error(error)\\n        let nsError = error as NSError\\n        fatalError(\\\"Unresolved error \\\\(nsError), \\\\(nsError.userInfo)\\\")\\n    }\\n}\\n\")), mdx(\"p\", null, \"We create a request using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fetchRequest\"), \" function that comes with the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreManagedObject\"), \". We then use that request to fetch genres from the database and convert the managed objects to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreViewModel\"), \" before returning them.\"), mdx(\"h2\", null, \"Updating AppEnvironment\"), mdx(\"p\", null, \"Open the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"AppEnvironment.swift,\")), \" and add the following lign below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"api\"), \" declaration.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"AppEnvironment.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"let coredata: CoredataServiceProtocol\\n\")), mdx(\"p\", null, \"Then replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"init\"), \" function with the following:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"AppEnvironment.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"init(decoder: JSONDecoder = .init(),\\n         api: ApiServiceProtocol = ApiService(),\\n         coredata: CoredataServiceProtocol = CoredataService()) {\\n        self.api = api\\n        self.decoder = decoder\\n        self.coredata = coredata\\n    }\\n\")), mdx(\"p\", null, \"Here we are initializing the coredata property declared above.\"), mdx(\"h2\", null, \"Updating Action\"), mdx(\"p\", null, \"We will have to add actions that the user will perform in order to fetch or save genres. Right now, we only have an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Api\"), \" enum in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Action\"), \" , let's add another one. This one will be for coredata related operations. So in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Action.swift\")), \" file, add the following:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Action.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"enum Coredata {\\n  case fetchGenres\\n  case saveGenres(_ genres: [GenreViewModel])\\n}\\n\")), mdx(\"p\", null, \"Then below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"api\"), \" case declaration, put this:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Action.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"case coredata(_ action: Coredata)\\n\")), mdx(\"h2\", null, \"Updating AppState\"), mdx(\"p\", null, \"Right now, we only have the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Api\"), \" struct in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppState\"), \" enum,  we'll need to add another  struct for coredata state. So add the following below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Api\"), \" struct.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"AppState.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"struct Coredata {\\n   var savedGenres: [GenreViewModel] = []\\n}\\n\")), mdx(\"p\", null, \"Now you should have an error caused by the new case in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Action\"), \" file. To fix it, add the following to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"dispatch\"), \" method in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Store\"), \" class.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"case .coredata(let action):\\n            break\\n\")), mdx(\"p\", null, \"So your \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"dispatch\"), \" method should be like this:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"func dispatch(_ action: Action) {        \\n        switch action {\\n        case .api(let action):\\n            ApiReducer.execute(action: action, state: apiState, environment: environment) { [weak self] state in\\n                self?.apiState = state\\n            }.store(in: &subscriptions)\\n        case .coredata(let action):\\n            break\\n        }\\n    }\\n\")), mdx(\"p\", null, \"We will implement that case shortly, but we need a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CoredataReducer\"), \" first.  So, this reducer will be similar to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiReducer\"), \" , but will handle coredata related operations.\"), mdx(\"h2\", null, \"Coredata Reducer\"), mdx(\"p\", null, \"Create a new swift file in \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Store\")), \" folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"CoredataReducer.\")), \" Then add the following code inside:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\n\\nstruct CoredataReducer {\\n\\n    static func execute(action: Action.Coredata, state: inout AppState.Coredata, environment: AppEnvironment) {\\n\\n        switch action {\\n\\n        case .fetchGenres:\\n            state.savedGenres = environment.coredata.fetchGenres()\\n        case .saveGenres(let genres):\\n            _ = environment.coredata.saveGenres(genres)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"The thing to pay attention to is the change in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"action\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" type, so here we are using \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Coredata\"), \" rather than \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Api\"), \".  Also we add a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"inout\"), \" keyword before \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppState.Coredata\"), \" to make the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" mutable inside the function body.\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"fetchGenres\"), \", we fetch genres from coredata and set them to the state\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"saveGenres\"), \", we saved genres that were passed in ( these are genres that the user selected on the genre selection screen).\")), mdx(\"p\", null, \"Now add the following in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Store.swift,\")), \" below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"apiState\"), \" declaration:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"@Published private(set) var coredataState = AppState.Coredata()\\n\")), mdx(\"p\", null, \"And replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"break\"), \" in the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"coredata\"), \" case with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"CoredataReducer.execute(action: action, state: &coredataState, environment: environment)\\n\")), mdx(\"p\", null, \"Here we are passing the required arguments, but the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"coredataState\"), \" is passed by reference with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"&\"), \" pre-pended before it because of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"inout\"), \" keyword in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"execute\"), \" declaration.\"), mdx(\"h2\", null, \"Saving selected genres\"), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreSelectionScreen\"), \" view, replace the content in the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".success\"), \" case with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"GenreSelectionContent(genres: genres, onContinue: {  selectedGenres in\\n      store.dispatch(.coredata(.saveGenres(selectedGenres)))\\n  })\\n\")), mdx(\"p\", null, \"What we are doing here is dispatching a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"coredata\"), \" action to the store to save selected genres. If you build the app, you will get an error caused by the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Persistence\")), \" not being in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"watchOS Extension\")), \" target. To fix that, select \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Persistence.swift\")), \" and open the file inspector, under \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Target Membership,\")), \" check the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"WatchOS Extension.\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612000091/StreamCast/iwjtndihtf2h9lmkanwt.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612000091/StreamCast/iwjtndihtf2h9lmkanwt.png\"\n  }))), mdx(\"p\", null, \"Now, you can build the project \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"(CMD + B)\")), \", and the error should be fixed.\"), mdx(\"p\", null, \"With this guys, we are done with lesson number 6. Let's move on to lesson number 7.\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#genre-managed-object-model","title":"Genre managed object model"},{"url":"#creating-an-entity-and-its-properties","title":"Creating an Entity and Its Properties"},{"url":"#updating-the-genre-view-model","title":"Updating the genre view model"},{"url":"#updating-persistencecontroller","title":"Updating PersistenceController"},{"url":"#coredata-service-protocol","title":"Coredata service protocol"},{"url":"#coredata-service","title":"Coredata service"},{"url":"#updating-appenvironment","title":"Updating AppEnvironment"},{"url":"#updating-action","title":"Updating Action"},{"url":"#updating-appstate","title":"Updating AppState"},{"url":"#coredata-reducer","title":"Coredata Reducer"},{"url":"#saving-selected-genres","title":"Saving selected genres"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-02-08T21:20:04.967Z","isNewLayout":true,"previews":{"previews":"{\n    \"Lesson7MenuLink3\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773193/StreamCast/lesson%207/xd2tq5uuuetqyffir17h.png\" ],\n    \"Lesson7HomeMenuList2\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773196/StreamCast/lesson%207/i0rsgwpq814tyw8ul8pi.png\" ],\n    \"Lesson7GenreView3\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773198/StreamCast/lesson%207/bugyogsacv1iophedg31.png\" ],\n    \"Lesson7SeeAllLink2\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773343/StreamCast/lesson%207/b3h5kt12ahr9mkveqgze.png\" ],\n    \"Lesson7GenreLinkView2\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773204/StreamCast/lesson%207/ghdyypqhmp7q9wuyqk4z.png\" ],\n    \"Lesson7HomeSectionHeaderView2\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773207/StreamCast/lesson%207/u2fqq5mozahlwajnxqrg.png\" ],\n    \"Lesson7HomeGenresCollection2\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773214/StreamCast/lesson%207/zrwsihfwzr9mtarcj7aw.png\" ],\n    \"Lesson7HomeGenresContent3\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773218/StreamCast/lesson%207/a9w8cuhvolb1zkspnd35.png\" ],\n    \"Lesson7HomeScreen1\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773225/StreamCast/lesson%207/qyfmfqboxho44u8bqeau.png\" ]\n}"},"slug":"lesson-7-home-screen-part-1","title":"Lesson #7:  Home Screen Part 1","updatedDate":null,"downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZkJWhXZ9c62IrjHapVncg1sBDRWdYL1mdBX","type":"zip","id":"185fe268-557f-52c7-8fdd-ea8c8f6441ec","name":"Source code"}],"allChapters":["Lesson7Introduction:Introduction","Lesson7HomeScreenPitch1:Home screen pitch","Lesson7Menus1:Menus","Lesson7HomeScreenFolders1:Home screen folders","Lesson7HomeMenuList1:HomeMenuList","Lesson7GenreView1:Genre view component","Lesson7SeeAllLink1:See all link element","Lesson7GenreLinkView1:GenreLinkView element","Lesson7HomeSectionHeaderView1:HomeSectionHeaderView component","Lesson7HomeGenresCollection1:HomeGenresCollection Component","Lesson7HomeGenresContent1:HomeGenresContent component","Lesson7HomeScreen1:HomeScreen Creation"],"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson7HomeScreenPitch1\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612775989/StreamCast/lesson%207/lxlpztaexvdmoaahv1tj.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612775989/StreamCast/lesson%207/lxlpztaexvdmoaahv1tj.png\"\n  })))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7UpdateColor1\\n// $$liquidcoderFilename-Color+\\n\\nimport Foundation\\nimport SwiftUI\\n\\nextension Color {\\n    static let pop = Color(\\\"pop\\\")\\n    static let background = Color(\\\"background\\\")\\n    static let overlay = Color(\\\"overlay\\\")\\n    static let text = Color(\\\"text\\\")\\n    static let shadow = Color(\\\"shadow\\\")\\n    // highlight-start\\n    #if os(watchOS)\\n    static let body = Color.white\\n    #endif\\n    // highlight-end\\n    #if os(iOS)\\n    static let body = Color(UIColor.secondaryLabel)\\n    static let border = Color(UIColor.separator)\\n    #elseif os(macOS)\\n    static let body = Color(NSColor.secondaryLabelColor)\\n    static let border = Color(NSColor.separatorColor)\\n    #endif\\n}\\n\")), mdx(\"div\", {\n    id: \"Lesson7UpdateColor2\",\n    className: \"code-block\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612613619/StreamCast/htyclxrvopfteezou7q1.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612613619/StreamCast/htyclxrvopfteezou7q1.png\"\n  })))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7Menus1\\n// $$liquidcoderFilename-MenuItem\\n\\nimport Foundation\\n\\nstruct MenuItem: Identifiable, Equatable {\\n\\n    var id: String {\\n        name\\n    }\\n    var name = \\\"\\\"\\n    var icon = \\\"\\\"\\n\\n    static var placeholder: MenuItem {\\n        MenuItem(name: \\\"Item\\\", icon: \\\"circlebadge.fill\\\")\\n    }\\n\\n    static var all: [MenuItem] {\\n\\n        [\\n            MenuItem(name: \\\"Profile\\\", icon: \\\"person.crop.circle.fill\\\"),\\n            MenuItem(name: \\\"Search\\\", icon: \\\"magnifyingglass.circle.fill\\\"),\\n            MenuItem(name: \\\"Downloaded\\\", icon: \\\"square.and.arrow.down.fill\\\"),\\n            MenuItem(name: \\\"Bookmarked\\\", icon: \\\"bookmark.fill\\\")\\n        ]\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7MenuLink1\\n// $$liquidcoderFilename-MenuLink\\n\\nimport SwiftUI\\n\\nstruct MenuLink: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct MenuLink_Previews: PreviewProvider {\\n    static var previews: some View {\\n        MenuLink()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7MenuLink2\\n// $$liquidcoderFilename-MenuLink\\n\\nimport SwiftUI\\n\\n// highlight-start\\nstruct MenuLink<Destination: View>: View  {\\n// highlight-end\\n\\n    // highlight-start\\n    var menu: MenuItem\\n    var destination: Destination\\n    // highlight-end\\n\\n    // highlight-start\\n    init(menu: MenuItem, @ViewBuilder destination: (() -> Destination)) {\\n        self.menu = menu\\n        self.destination = destination()\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct MenuLink_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        // highlight-start\\n        Group {\\n            MenuLink(menu: MenuItem.placeholder, destination: {\\n                Text(\\\"Hello world\\\")\\n            }).previewLayout(.sizeThatFits)\\n            MenuLink(menu: MenuItem.placeholder, destination: {\\n                Text(\\\"Hello world\\\")\\n            }).preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7MenuLink3\\n// $$liquidcoderFilename-MenuLink\\n\\nimport SwiftUI\\n\\nstruct MenuLink<Destination: View>: View  {\\n\\n    var menu: MenuItem\\n    var destination: Destination\\n\\n    init(menu: MenuItem, @ViewBuilder destination: (() -> Destination)) {\\n        self.menu = menu\\n        self.destination = destination()\\n    }\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        NavigationLink(destination: destination) {\\n            Label(menu.name, systemImage: menu.icon)\\n                .fixedSize()\\n                .padding(.horizontal)\\n                .padding(.vertical, 10)\\n                .foregroundColor(.accentColor)\\n                .background(Color.accentColor.opacity(0.2))\\n                .cornerRadius(10)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7HomeMenuList1\\n// $$liquidcoderFilename-HomeMenuList\\n\\nimport SwiftUI\\n\\nstruct HomeMenuList: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct HomeMenuList_Previews: PreviewProvider {\\n    static var previews: some View {\\n        HomeMenuList()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7HomeMenuList2\\n// $$liquidcoderFilename-HomeMenuList\\n\\nimport SwiftUI\\n\\nstruct HomeMenuList: View {\\n    var body: some View {\\n        // highlight-start\\n        ScrollView {\\n            HStack(spacing: 15) {\\n                ForEach(MenuItem.all) { menu in\\n                    MenuLink(menu: menu, destination: {\\n                        Text(\\\"To be implemented\\\")\\n                    })\\n                }\\n            }\\n        }\\n        // highlight-end\\n    }\\n}\\n\\nstruct HomeMenuList_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        // highlight-start\\n        Group {\\n            HomeMenuList()\\n                .frame(height: 100)\\n                .previewLayout(.sizeThatFits)\\n            HomeMenuList()\\n                .preferredColorScheme(.dark)\\n                .frame(height: 100)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7GenreView2\\n// $$liquidcoderFilename-GenreView\\n\\nimport SwiftUI\\n\\nstruct GenreView: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct GenreView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        GenreView()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7GenreView3\\n// $$liquidcoderFilename-GenreView\\n\\nimport SwiftUI\\n\\nstruct GenreView: View {\\n\\n    // highlight-start\\n    var genre: GenreViewModel\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        HStack {\\n            Text(genre.name)\\n                .bold()\\n                .multilineTextAlignment(.center)\\n\\n            Spacer()\\n\\n            Image(systemName: \\\"chevron.forward\\\")\\n                .resizable()\\n                .aspectRatio(contentMode: .fit)\\n                .frame(width: 15, height: 15)\\n        } .padding(.horizontal)\\n        .foregroundColor(Color.body)\\n        // highlight-end\\n    }\\n}\\n\\nstruct GenreView_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        // highlight-start\\n        Group {\\n            GenreView(genre: GenreViewModel.placeholder)\\n                .background(Color.background)\\n                .colorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n\\n            GenreView(genre: GenreViewModel.placeholder)\\n                .colorScheme(.light)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7SeeAllLink1\\n// $$liquidcoderFilename-SeeAllLink\\n\\nimport SwiftUI\\n\\nstruct SeeAllLink: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct SeeAllLink_Previews: PreviewProvider {\\n    static var previews: some View {\\n        SeeAllLink()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7SeeAllLink2\\n// $$liquidcoderFilename-SeeAllLink\\n\\nimport SwiftUI\\n\\n// highlight-start\\nstruct SeeAllLink<Destination: View>: View {\\n// highlight-end\\n\\n    // highlight-start\\n    private var destination: Destination\\n    // highlight-end\\n\\n    // highlight-start\\n    init( @ViewBuilder destination:  (() -> Destination)) {\\n        self.destination = destination()\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        // highlight-start\\n        NavigationLink(destination: destination) {\\n            Text(\\\"See all\\\")\\n        }\\n        // highlight-end\\n    }\\n}\\n\\nstruct SeeAllLink_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            SeeAllLink(destination: {\\n                Text(\\\"Seen all!\\\")\\n            }).colorScheme(.dark)\\n            .previewLayout(.sizeThatFits)\\n\\n            SeeAllLink(destination: {\\n                Text(\\\"Seen all!\\\")\\n            })\\n            .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7GenreLinkView1\\n// $$liquidcoderFilename-GenreLinkView\\n\\nimport SwiftUI\\n\\nstruct GenreLinkView: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct GenreLinkView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        GenreLinkView()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7GenreLinkView2\\n// $$liquidcoderFilename-GenreLinkView\\n\\nimport SwiftUI\\n\\nstruct GenreLinkView: View {\\n\\n    // highlight-start\\n    var genre: GenreViewModel\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        NavigationLink(\\n            destination: Text(\\\"To be implemented\\\"),\\n            label: {\\n                GenreView(genre: genre)\\n                    .frame(minWidth: 250, maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)\\n                    .background(Color.overlay)\\n                    .cornerRadius(15)\\n            })\\n        // highlight-end\\n    }\\n}\\n\\nstruct GenreLinkView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n\\n            GenreLinkView(genre: GenreViewModel.placeholder)\\n                .frame(width: 250, height: 70, alignment: .center)\\n                .previewLayout(.sizeThatFits)\\n\\n            GenreLinkView(genre: GenreViewModel.placeholder)\\n                .frame(width: 250, height: 70, alignment: .center)\\n                .colorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7HomeSectionHeaderView1\\n// $$liquidcoderFilename-HomeSectionHeaderView\\n\\nimport SwiftUI\\n\\nstruct HomeSectionHeaderView: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct HomeSectionHeaderView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        HomeSectionHeaderView()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7HomeSectionHeaderView2\\n// $$liquidcoderFilename-HomeSectionHeaderView\\n\\nimport SwiftUI\\n\\nstruct HomeSectionHeaderView: View {\\n\\n    // highlight-start\\n    var title: String\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        HStack {\\n            Text(title)\\n                .font(.title2)\\n                .bold()\\n            Spacer()\\n\\n            SeeAllLink(destination: {\\n                Text(\\\"To be implemented!\\\")\\n            }).padding(.vertical, 7)\\n            .cornerRadius(10)\\n            .background(Color.clear)\\n        }.padding(.horizontal)\\n        // highlight-end\\n    }\\n}\\n\\nstruct HeaderView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            HomeSectionHeaderView(title: \\\"Trending\\\")\\n                .background(Color.background)\\n                .colorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n\\n            HomeSectionHeaderView(title: \\\"Trending\\\")\\n                .colorScheme(.light)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7HomeGenresCollection1\\n// $$liquidcoderFilename-HomeGenresCollection\\n\\nimport SwiftUI\\n\\nstruct HomeGenresCollection: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct HomeGenresCollection_Previews: PreviewProvider {\\n    static var previews: some View {\\n        HomeGenresCollection()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7HomeGenresCollection2\\n// $$liquidcoderFilename-HomeGenresCollection\\n\\nimport SwiftUI\\n\\nstruct HomeGenresCollection: View {\\n\\n    // highlight-start\\n    var genres: [GenreViewModel]\\n    // highlight-end\\n\\n    // highlight-start\\n    private let gridItems: [GridItem] =  Array(repeating: .init(.adaptive(minimum: 40), spacing: 20), count: 2)\\n    // highlight-end\\n\\n    var body: some View {\\n        // highlight-start\\n        ScrollView(.horizontal, showsIndicators: false) {\\n            LazyHGrid(rows: gridItems, alignment: .center, spacing: 20) {\\n                ForEach(genres) { genre in\\n                    GenreLinkView(genre: genre)\\n                }\\n            }.padding(.horizontal)\\n        }.frame(height: 180, alignment: .leading)\\n        // highlight-end\\n    }\\n}\\n\\nstruct HomeGenresCollection_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            HomeGenresCollection(genres: GenreViewModel.listPlaceholders)\\n                .background(Color.background)\\n                .previewLayout(.sizeThatFits)\\n                .colorScheme(.dark)\\n\\n            HomeGenresCollection(genres: GenreViewModel.listPlaceholders)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7HomeGenresContent1\\n// $$liquidcoderFilename-HomeGenresContent\\n\\nimport SwiftUI\\n\\nstruct HomeGenresContent: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct HomeGenresContent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        HomeGenresContent()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7HomeGenresContent2\\n// $$liquidcoderFilename-HomeGenresContent\\n\\nimport SwiftUI\\n\\nstruct HomeGenresContent: View {\\n\\n    // highlight-start\\n    @EnvironmentObject private var store: Store\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n\\n    // highlight-start\\n    private func createContent(genres: [GenreViewModel]) -> some View {\\n        VStack {\\n            HomeSectionHeaderView(title: \\\"All genres\\\")\\n            HomeGenresCollection(genres: genres)\\n        }\\n    }\\n    // highlight-end\\n}\\n\\nstruct HomeGenresContent_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        Group {\\n            HomeGenresContent()\\n                .background(Color.background)\\n                .previewLayout(.sizeThatFits)\\n                .colorScheme(.dark)\\n\\n            HomeGenresContent()\\n                .background(Color.background)\\n                .previewLayout(.sizeThatFits)\\n                .colorScheme(.light)\\n                        // highlight-start\\n        }.environmentObject(Store())\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7HomeGenresContent3\\n// $$liquidcoderFilename-HomeGenresContent\\n\\nimport SwiftUI\\n\\nstruct HomeGenresContent: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    var body: some View {\\n        // highlight-start\\n        switch store.apiState.allGenres {\\n        case .loading:\\n            createContent(genres: GenreViewModel.listPlaceholders)\\n                .redacted(reason: .placeholder)\\n                .onAppear {\\n                    store.dispatch(.api(.fetchGenres(\\\"genres\\\")))\\n                }\\n        case .success(let genres):\\n            createContent(genres: genres)\\n        case .failure:\\n            Text(\\\"Failed to Load genres...\\\")\\n        }\\n        // highlight-end\\n    }\\n\\n    private func createContent(genres: [GenreViewModel]) -> some View {\\n        VStack {\\n            HomeSectionHeaderView(title: \\\"All genres\\\")\\n            HomeGenresCollection(genres: genres)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7HomeScreen1\\n// $$liquidcoderFilename-HomeScreen\\n\\nimport SwiftUI\\n\\nstruct HomeScreen: View {\\n    var body: some View {\\n        // highlight-start\\n        ScrollView {\\n            LazyVStack(spacing: 20) {\\n                HomeMenuList()\\n                HomeGenresContent()\\n            }\\n        }\\n        // highlight-end\\n    }\\n}\\n\\nstruct HomeScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            HomeScreen()\\n                .colorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n                .environmentObject(Store())\\n            HomeScreen()\\n                .previewLayout(.sizeThatFits)\\n                .environmentObject(Store())\\n        }\\n        // highlight-end\\n    }\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson7Introduction-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey guys welcome back to lesson #7, in the previous lesson we had a little introduction to coredata. We also updated the state, the action and the store as well as saving selected genres on the selection screen to coredata. You need lesson 6 in order to start lesson 7. Let's go\")), mdx(\"div\", {\n    id: \"Lesson7UpdateColor1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Before we get started, we need to make some change to the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Color+\")), \"  (The \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Color\")), \" extension) file.\"), mdx(\"p\", null, \"Add The code highlighted code in the struct.\")), mdx(\"div\", {\n    id: \"Lesson7UpdateColor2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next, we will need to change the targets membership of the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Color+\")), \" file. So, while the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Color+.swift\")), \" is selected, open the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"file inspector tab in\"), \" XCode, and check \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"watchOS Extension target.\")))), mdx(\"div\", {\n    id: \"Lesson7HomeScreenPitch1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Home screen pitch\"), mdx(\"p\", null, \"The home screen will be composed of different  views depending on the platform we are currently running on. Here is an illustration to give you an idea of what we are trying to accomplish.\")), mdx(\"div\", {\n    id: \"Lesson7Menus1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Menus\"), mdx(\"p\", null, \"We will use menus on the home screen as well as the sidebar on iPadOS and macOS.  So,  let's first define the menu model. In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Models\")), \" folder, create a swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"MenuItem\")), \" containing the following code. \"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Just check the iOS and macOS targets.\")), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Menu\"), \" is just a simple struct that conforms to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Identifiable\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Equatable\"), \" protocol in order to use it in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ForEarch\"), \" view as well as checking for equality between multiple of its instances respectively.\")), mdx(\"div\", {\n    id: \"Lesson7HomeScreenFolders1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Home screen folders\"), mdx(\"p\", null, \"Create a new folder inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens\")), \" folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Home\")), \", this will contain all views that will compose the home screen. Now create the 3 folders ( \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Styles\")), \", \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Components\")), \", \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Elements\")), \") inside it. \"), mdx(\"p\", null, \"We may add \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Modifiers\")), \"  if need be, but for now we just need those 3.\")), mdx(\"div\", {\n    id: \"Lesson7MenuLink1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Create a new swiftUI file in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Home\")), \" \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"\\u2192\")), \" \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Elements\")), \" folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"MenuLink\")), \" .\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Just check the iOS and macOS targets.\"))), mdx(\"div\", {\n    id: \"Lesson7MenuLink2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"This view is a custom version of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NavigationLink\"), \" view in swiftUI, and it will re-used with different types of destinations that's why it's generic.\"), mdx(\"p\", null, \"We obviously need a menu because it's a menu view, and a destination to navigate to.\"), mdx(\"p\", null, \"In the initialiser,  the destination is wrapped in a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ViewBuilder\"), \" to allow complex views to be passed in its closure just like any other container in swift UI.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"If you want to see how this view will be used, take a look in the preview. We have made some modifications. The destination is the view that we will navigate to when the menu link is clicked.\"))), mdx(\"div\", {\n    id: \"Lesson7MenuLink3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \" we just return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NaviagationLink\"), \" with  a styled label as content.\")), mdx(\"div\", {\n    id: \"Lesson7HomeMenuList1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"HomeMenuList\"), mdx(\"p\", null, \"Create a new swiftUI file in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Home\")), \" \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"\\u2192\")), \" \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Components\")), \" folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"HomeMenuList\")), \" .\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Just check the iOS and macOS targets.\"))), mdx(\"div\", {\n    id: \"Lesson7HomeMenuList2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We are creating a horizontal scrolling list of menu links.\")), mdx(\"div\", {\n    id: \"Lesson7GenreView1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Genre view component\"), mdx(\"p\", null, \"The genres section is the one below the menu on the iPhone. The section will be composed of a header and a horizontal scrolling list of genre elements. Like I told you, let's start creating the elements, then from them we will create components and finally a screen.\")), mdx(\"div\", {\n    id: \"Lesson7GenreView2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now, in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI\")), \" folder, add a new swiftUI file inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ShareUI/Components\")), \" folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreView.\")), \" Make sure you check the 3 targets except the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"watchOS.\")), \" Only this component will be shared to all 3 targets, so the remaining views we will create in this lesson will only be either for iOS only or macOS only or both.\")), mdx(\"div\", {\n    id: \"Lesson7GenreView3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"This view will be shared by multiple views in different screens that's why we put it in the the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI\")), \" folder. \"), mdx(\"p\", null, \"We declare a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"genre\"), \" property because this is a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreView\"), \" obviously. The 2 views are aligned horizontally, hence the use of a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HStack\"), \" as a parent container.\")), mdx(\"div\", {\n    id: \"Lesson7SeeAllLink1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"See all link element\"), mdx(\"p\", null, \"Add a new swiftUI file in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Home \\u2192 Elements\")), \" named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SeeAllLink.\"))), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Just check the iOS and macOS targets.\"))), mdx(\"div\", {\n    id: \"Lesson7SeeAllLink2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"This  element will be shared by multiple views. It's generic with the generic parameter being the destination view to navigate to since this is a custom version of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NavigationLink\"), \".\")), mdx(\"div\", {\n    id: \"Lesson7GenreLinkView1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"GenreLinkView element\"), mdx(\"p\", null, \"Add a new swiftUI file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreLinkView\")), \" in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI/Elements\")), \" folder.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Just check the iOS and macOS targets.\"))), mdx(\"div\", {\n    id: \"Lesson7GenreLinkView2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"This view is using the shared genre view component that we've just created. We use a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NavigationLink\"), \" \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"**\"), \"label because later on we will create a detail screen  to navigate to. Right now , we are putting that \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text(\\\"To be implemented\\\")\"), \" as placeholder.\")), mdx(\"div\", {\n    id: \"Lesson7HomeSectionHeaderView1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"HomeSectionHeaderView component\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Components\")), \" folder of \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Home\")), \" , add swiftUI file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"HomeSectionHeaderView.\"))), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Just check the iOS and macOS targets.\"))), mdx(\"div\", {\n    id: \"Lesson7HomeSectionHeaderView2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"This view aligns a title and the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SeeAllLink\"), \" view horizontally to be used as headers for each home sections.\")), mdx(\"div\", {\n    id: \"Lesson7HomeGenresCollection1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"HomeGenresCollection Component\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Components\")), \" folder of \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Home\")), \" , add a swiftUI file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"HomeGenresCollection\"))), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Just check the iOS and macOS targets.\"))), mdx(\"div\", {\n    id: \"Lesson7HomeGenresCollection2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"This view will create an horizontally scrolling list of genres from which a user can select a single one to present a list of podcast related to it. We are using a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LazyHGrid\"), \" with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"gridItems\"), \" declare above.\")), mdx(\"div\", {\n    id: \"Lesson7HomeGenresContent1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"HomeGenresContent component\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Home \\u2192 Components\")), \" folder, add the following a new swiftUI file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"HomeGenresContent.\"))), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Just check the iOS and macOS targets.\"))), mdx(\"div\", {\n    id: \"Lesson7HomeGenresContent2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We first bring in the store to help us load genres. The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createContent\"), \" function stacks the header and genre collection view vertically.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"In the preview, we add the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"environmentObject\"), \" in order for the previews to work.\"))), mdx(\"div\", {\n    id: \"Lesson7HomeGenresContent3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In body, we switch on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"apiState\"), \"'s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"allGenres\"), \" property, and return appropriate content for each case.  When the loading content appears, which is the placeholder content, we dispatch an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"api\"), \" action to fetch genres.\")), mdx(\"div\", {\n    id: \"Lesson7HomeScreen1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"HomeScreen creation\"), mdx(\"p\", null, \"The above code is doing the same thing we did in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreSelectionScreen\"), \" view, but  with this we are showing a 2 rows horizontal scrolling list. \"), mdx(\"p\", null, \"As you can see, we are rendering the 2 components we've just created. However, this view will change soon as we are not done with it yet.\"), mdx(\"p\", null, \"Let's finish this lesson here, in the next one we will move away from the home for a little bit to create  the image fetching mechanism and view before getting back and finish off the home screen, see you in lesson 8. Happy coding!\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#home-screen-pitch","title":"Home screen pitch"},{"url":"#menus","title":"Menus"},{"url":"#home-screen-folders","title":"Home screen folders"},{"url":"#homemenulist","title":"HomeMenuList"},{"url":"#genre-view-component","title":"Genre view component"},{"url":"#see-all-link-element","title":"See all link element"},{"url":"#genrelinkview-element","title":"GenreLinkView element"},{"url":"#homesectionheaderview-component","title":"HomeSectionHeaderView component"},{"url":"#homegenrescollection-component","title":"HomeGenresCollection Component"},{"url":"#homegenrescontent-component","title":"HomeGenresContent component"},{"url":"#homescreen-creation","title":"HomeScreen creation"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Hey guys welcome back to lesson #7, in the previous lesson we had an introduction to coredata, we also updated the state, the action and  the store as well as saving selected genres on the selection screen to coredata. You need lesson 6 in order to start lesson 7. Let's get started.\"), mdx(\"h2\", null, \"watchOS Color+\"), mdx(\"p\", null, \"Before we get started, we need to make some change to the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Color+\")), \"  (The \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Color\")), \" extension) file.\"), mdx(\"p\", null, \"Add The code highlighted code in the struct.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7UpdateColor1\\n// $$liquidcoderFilename-Color+\\n\\nimport Foundation\\nimport SwiftUI\\n\\nextension Color {\\n    static let pop = Color(\\\"pop\\\")\\n    static let background = Color(\\\"background\\\")\\n    static let overlay = Color(\\\"overlay\\\")\\n    static let text = Color(\\\"text\\\")\\n    static let shadow = Color(\\\"shadow\\\")\\n    // highlight-start\\n    #if os(watchOS)\\n    static let body = Color.white\\n    #endif\\n    // highlight-end\\n    #if os(iOS)\\n    static let body = Color(UIColor.secondaryLabel)\\n    static let border = Color(UIColor.separator)\\n    #elseif os(macOS)\\n    static let body = Color(NSColor.secondaryLabelColor)\\n    static let border = Color(NSColor.separatorColor)\\n    #endif\\n}\\n\")), mdx(\"p\", null, \"Next, we will need to change the targets membership of the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Color+\")), \" file. So, while the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Color+.swift\")), \" is selected, open the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"file inspector tab in\"), \" XCode, and check \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"watchOS Extension target.\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612613619/StreamCast/htyclxrvopfteezou7q1.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612613619/StreamCast/htyclxrvopfteezou7q1.png\"\n  }))), mdx(\"h2\", null, \"Home screen pitch\"), mdx(\"p\", null, \"The home screen will be composed of differents other views depending on the platform we are currently running on. Here is an illustration to give you an idea of what we are trying to accomplish\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612775989/StreamCast/lesson%207/lxlpztaexvdmoaahv1tj.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612775989/StreamCast/lesson%207/lxlpztaexvdmoaahv1tj.png\"\n  }))), mdx(\"h2\", null, \"Menus\"), mdx(\"p\", null, \"We will use menus on the home screen as well as the sidebar on iPadOS and macOS.  So,  let's first define the menu model. In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Models\")), \" folder, create a swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"MenuItem\")), \" containing the following code:\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"MenuItem.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\n\\nstruct MenuItem: Identifiable, Equatable {\\n\\n    var id: String {\\n        name\\n    }\\n    var name = \\\"\\\"\\n    var icon = \\\"\\\"\\n\\n    static var placeholder: MenuItem {\\n        MenuItem(name: \\\"Item\\\", icon: \\\"circlebadge.fill\\\")\\n    }\\n\\n    static var all: [MenuItem] {\\n\\n        [\\n            MenuItem(name: \\\"Profile\\\", icon: \\\"person.crop.circle.fill\\\"),\\n            MenuItem(name: \\\"Search\\\", icon: \\\"magnifyingglass.circle.fill\\\"),\\n            MenuItem(name: \\\"Downloaded\\\", icon: \\\"square.and.arrow.down.fill\\\"),\\n            MenuItem(name: \\\"Bookmarked\\\", icon: \\\"bookmark.fill\\\")\\n        ]\\n    }\\n}\\n\")), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Menu\"), \" is just a simple struct that conforms to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Identifiable\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Equatable\"), \" protocol in order to use it in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ForEarch\"), \" view as well as checking for equality between multiple of its instances respectively.\"), mdx(\"h2\", null, \"Home screen folders\"), mdx(\"p\", null, \"Create a new folder inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens\")), \" folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Home\")), \", this will contain all views that will compose the home screen. Now create the 3 folders ( \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Styles\")), \", \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Components\")), \", \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Elements\")), \") inside it. \"), mdx(\"p\", null, \"We may add \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Modifiers\")), \"  if need be, but for now we just need those 3.\"), mdx(\"h2\", null, \"MenuLink element\"), mdx(\"p\", null, \"Create a new swiftUI file in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Home\")), \" \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"\\u2192\")), \" \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Elements\")), \" folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"MenuLink\")), \" containing the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7MenuLink3\\n// $$liquidcoderFilename-MenuLink\\n\\nimport SwiftUI\\n\\nstruct MenuLink<Destination: View>: View {\\n    var menu: MenuItem\\n    var destination: Destination\\n\\n    init(menu: MenuItem, @ViewBuilder destination: (() -> Destination)) {\\n        self.menu = menu\\n        self.destination = destination()\\n    }\\n\\n    var body: some View {\\n\\n        NavigationLink(destination: destination) {\\n            Label(menu.name, systemImage: menu.icon)\\n                .fixedSize()\\n                .padding(.horizontal)\\n                .padding(.vertical, 10)\\n                .foregroundColor(.accentColor)\\n                .background(Color.accentColor.opacity(0.2))\\n                .cornerRadius(10)\\n        }\\n    }\\n}\\n\\nstruct MenuLink_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            MenuLink(menu: MenuItem.placeholder, destination: {\\n                Text(\\\"Hello world\\\")\\n            }).previewLayout(.sizeThatFits)\\n            MenuLink(menu: MenuItem.placeholder, destination: {\\n                Text(\\\"Hello world\\\")\\n            }).preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"Here is what we are doing here:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"This view is  a custom version of the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"NavigationLink\"), \" view in swiftUI and it will re-used with different types of destinations that's why it's generic.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We obviously need a menu because it's a menu view, and a destination to navigate to.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the initiliser,  the destination is wrapped in a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ViewBuilder\"), \" to allow complex views to be passed in its closure just like any other container in swift UI\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"body\"), \" we just return a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"NaviagationLink\"), \" with  a styled label as content.\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773193/StreamCast/lesson%207/xd2tq5uuuetqyffir17h.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773193/StreamCast/lesson%207/xd2tq5uuuetqyffir17h.png\"\n  }))), mdx(\"h2\", null, \"HomeMenuList\"), mdx(\"p\", null, \"Create a new swiftUI file in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Home\")), \" \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"\\u2192\")), \" \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Components\")), \" folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"HomeMenuList\")), \" .\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Just check the iOS and macOS targets.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson7HomeMenuList2\\n// $$liquidcoderFilename-HomeMenuList\\n\\nimport SwiftUI\\n\\nstruct HomeMenuList: View {\\n    var body: some View {\\n        ScrollView(.horizontal, showsIndicators: false) {\\n            HStack(spacing: 15) {\\n                ForEach(MenuItem.all) { menu in\\n                    MenuLink(menu: menu, destination: {\\n                        Text(\\\"To be implemented\\\")\\n                    })\\n                }\\n            }.padding(.horizontal)\\n        }\\n    }\\n}\\n\\nstruct HomeMenuList_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            HomeMenuList()\\n                .frame(height: 100)\\n                .previewLayout(.sizeThatFits)\\n            HomeMenuList()\\n                .preferredColorScheme(.dark)\\n                .frame(height: 100)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"We are creating a horizontal scrolling list of menu links.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773196/StreamCast/lesson%207/i0rsgwpq814tyw8ul8pi.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773196/StreamCast/lesson%207/i0rsgwpq814tyw8ul8pi.png\"\n  }))), mdx(\"h2\", null, \"Genre view component\"), mdx(\"p\", null, \"The genres section is the one below the menu on the iphone. The section will be composed of a header and a horizontal scrolling list of genre elements. Like I told you, let's start creating the elements, then from them we will create components and finally a screen.\"), mdx(\"p\", null, \"Now, in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI\")), \" folder, add a new swiftUI file inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Components\")), \" folder \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"**\"), \"named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreView.\")), \" Make sure you check the 3 targets except the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"watchOS.\")), \" Only this component will be shared to all 3 targets, so the remaining views we will create in this lesson will only be either for iOS only or macOS only or both.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreView.swift.\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct GenreView: View {\\n    var genre: GenreViewModel\\n\\n    var body: some View {\\n            HStack {\\n                Text(genre.name)\\n                    .bold()\\n                    .multilineTextAlignment(.center)\\n\\n                Spacer()\\n\\n                Image(systemName: \\\"chevron.forward\\\")\\n                    .resizable()\\n                    .aspectRatio(contentMode: .fit)\\n                    .frame(width: 15, height: 15)\\n            } .padding(.horizontal)\\n            .foregroundColor(Color.body)\\n    }\\n}\\n\\nstruct GenreView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            GenreView(genre: GenreViewModel.placeholder)\\n                .background(Color.background)\\n                .colorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n\\n            GenreView(genre: GenreViewModel.placeholder)\\n                .colorScheme(.light)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"This view will be shared by multiple views in different screens that's why we put it in the the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI\")), \" foldder. We declare a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"genre\"), \" property because this is a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreView\"), \" obviously. The 2 views are aligned horizontally, hence the use of a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HStack\"), \" as a parent container.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773198/StreamCast/lesson%207/bugyogsacv1iophedg31.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773198/StreamCast/lesson%207/bugyogsacv1iophedg31.png\"\n  }))), mdx(\"h2\", null, \"See all link element\"), mdx(\"p\", null, \"Add a new swiftUI file in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI \\u2192 Elements\")), \" named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SeeAllLink,\")), \"  and with the following content inside\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SeeAllLink.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct SeeAllLink<Destination: View>: View {\\n\\n    private var destination: Destination\\n\\n    init( @ViewBuilder destination:  (() -> Destination)) {\\n        self.destination = destination()\\n    }\\n\\n    var body: some View {\\n        NavigationLink(destination: destination) {\\n            Text(\\\"See all\\\")\\n        }\\n    }\\n}\\n\\nstruct SeeAllLink_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        Group {\\n            SeeAllLink(destination: {\\n                Text(\\\"Seen all!\\\")\\n            }).previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"This  element will be shared by multiple views. It's generic with the generic parameter being the destination view to navigate to since this is a custom version of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NavigationLink\"), \".\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773343/StreamCast/lesson%207/b3h5kt12ahr9mkveqgze.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773343/StreamCast/lesson%207/b3h5kt12ahr9mkveqgze.png\"\n  }))), mdx(\"h2\", null, \"GenreLinkView element\"), mdx(\"p\", null, \"Add a new swiftUI file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreLinkView\")), \" in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI/Elements\")), \" folder.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"HomeGenreView.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct GenreLinkView: View {\\n\\n    var genre: GenreViewModel\\n\\n    var body: some View {\\n\\n        NavigationLink(\\n            destination: Text(\\\"To be implemented\\\"),\\n            label: {\\n                GenreView(genre: genre)\\n                    .frame(minWidth: 250, maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)\\n                    .background(Color.overlay)\\n                    .cornerRadius(15)\\n            })\\n\\n    }\\n}\\n\\nstruct GenreLinkView_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        Group {\\n\\n            GenreLinkView(genre: GenreViewModel.placeholder)\\n                .frame(width: 250, height: 70, alignment: .center)\\n                .previewLayout(.sizeThatFits)\\n\\n            GenreLinkView(genre: GenreViewModel.placeholder)\\n                .frame(width: 250, height: 70, alignment: .center)\\n                .colorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"This view is using the shared genre view component that we've just created. We use a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NavigationLink\"), \" \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"**\"), \"label because later on we will create a detail screen  to navigate to. Right now , we are putting that \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text(\\\"To be implemented\\\")\"), \" as placeholder.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773204/StreamCast/lesson%207/ghdyypqhmp7q9wuyqk4z.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773204/StreamCast/lesson%207/ghdyypqhmp7q9wuyqk4z.png\"\n  }))), mdx(\"h2\", null, \"HomeSectionHeaderView component\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Components\")), \" folder of \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Home\")), \" , add swiftUI file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"HomeSectionHeaderView.\"))), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"HomeSectionHeaderView.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct HomeSectionHeaderView: View {\\n    var title: String\\n\\n    var body: some View {\\n        HStack {\\n            Text(title)\\n                .font(.title2)\\n                .bold()\\n            Spacer()\\n\\n            SeeAllLink(destination: {\\n                Text(\\\"To be implemented!\\\")\\n            }).padding(.vertical, 7)\\n            .cornerRadius(10)\\n            .background(Color.clear)\\n\\n        }.padding(.horizontal)\\n    }\\n}\\n\\nstruct HeaderView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            HomeSectionHeaderView(title: \\\"Trending\\\")\\n                .background(Color.background)\\n                .colorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n\\n            HomeSectionHeaderView(title: \\\"Trending\\\")\\n                .colorScheme(.light)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773207/StreamCast/lesson%207/u2fqq5mozahlwajnxqrg.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773207/StreamCast/lesson%207/u2fqq5mozahlwajnxqrg.png\"\n  }))), mdx(\"h2\", null, \"HomeGenresCollection Component\"), mdx(\"p\", null, \"Now let's utilise the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"HomeGenreView\")), \" element. In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Components\")), \" folder of \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Home\")), \" , add a swiftUI file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"HomeGenresCollection\"))), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"HomeGenresCollection.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct HomeGenresCollection: View {\\n\\n    var genres: [GenreViewModel]\\n\\n    private let gridItems: [GridItem] =  Array(repeating: .init(.adaptive(minimum: 40), spacing: 20), count: 2)\\n\\n    var body: some View {\\n\\n        ScrollView(.horizontal, showsIndicators: false) {\\n            LazyHGrid(rows: gridItems, alignment: .center, spacing: 20) {\\n                ForEach(genres) { genre in\\n                    HomeGenreView(genre: genre)\\n                }\\n            }.padding(.horizontal)\\n        }.frame(height: 180, alignment: .leading)\\n    }\\n}\\n\\nstruct HomeGenresCollection_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            HomeGenresCollection(genres: GenreViewModel.listPlaceholders)\\n                .background(Color.background)\\n                .previewLayout(.sizeThatFits)\\n                .colorScheme(.dark)\\n\\n            HomeGenresCollection(genres: GenreViewModel.listPlaceholders)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"This view will create an horizontally scrolling list of genres from which a user can select a single one to present a list of podcast related to it. We are using a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LazyHGrid\"), \" with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"gridItems\"), \" declare above.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773210/StreamCast/lesson%207/gezc9wl9v0abtocwrmxe.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773210/StreamCast/lesson%207/gezc9wl9v0abtocwrmxe.png\"\n  }))), mdx(\"h2\", null, \"HomeGenresContent component\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Home \\u2192 Components\")), \" folder, add the following a new swiftUI file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"HomeGenresContent.\"))), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"HomeGenresContent.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct HomeGenresContent: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    var body: some View {\\n\\n        switch store.apiState.allGenres {\\n        case .loading:\\n        createContent(genres: GenreViewModel.listPlaceholders)\\n                .redacted(reason: .placeholder)\\n                                .onAppear {\\n                    store.dispatch(.api(.fetchGenres(\\\"genres\\\")))\\n                }\\n        case .success(let genres):\\n            createContent(genres: genres)\\n        case .failure:\\n            Text(\\\"Failed to Load genres...\\\")\\n        }\\n    }\\n\\n    private func createContent(genres: [GenreViewModel]) -> some View {\\n        VStack {\\n            HomeSectionHeaderView(title: \\\"All genres\\\")\\n            HomeGenresCollection(genres: genres)\\n        }\\n    }\\n}\\n\\nstruct HomeGenresContent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            HomeGenresContent()\\n                .background(Color.background)\\n                .previewLayout(.sizeThatFits)\\n                .colorScheme(.dark)\\n\\n            HomeGenresContent()\\n                .background(Color.background)\\n                .previewLayout(.sizeThatFits)\\n                .colorScheme(.light)\\n        }.environmentObject(Store())\\n    }\\n}\\n\")), mdx(\"p\", null, \"The above code is doing the same thing we did in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreSelectionScreen\"), \" view, but  with this we are showing a 2 rows horinzotal scrolling list. \"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773218/StreamCast/lesson%207/a9w8cuhvolb1zkspnd35.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773218/StreamCast/lesson%207/a9w8cuhvolb1zkspnd35.png\"\n  }))), mdx(\"h2\", null, \"HomeScreen creation\"), mdx(\"p\", null, \"Just like we did for the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreSelectionScreen\"), \" , create a swiftUI file in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Home\")), \" folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"HomeScreen\"))), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"HomeScreen.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct HomeScreen: View {\\n    var body: some View {\\n        ScrollView {\\n            LazyVStack(spacing: 20) {\\n                HomeMenuList()\\n                HomeGenresContent()\\n            }\\n        }\\n    }\\n}\\n\\nstruct HomeScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        HomeScreen()\\n            .environmentObject(Store())\\n    }\\n}\\n\")), mdx(\"p\", null, \"As you can see, we are rendering the 2 components we've just created. However, this view will change soon as we are not done with it yet.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773225/StreamCast/lesson%207/qyfmfqboxho44u8bqeau.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612773225/StreamCast/lesson%207/qyfmfqboxho44u8bqeau.png\"\n  }))), mdx(\"p\", null, \"Let's finish this lesson here, in the next one we will move away from the home for a little bit to create  the image fetching mechanism and view before getting back and finish off the home screen, see you in lesson 8. Happy coding!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#watchos-color","title":"watchOS Color+"},{"url":"#home-screen-pitch","title":"Home screen pitch"},{"url":"#menus","title":"Menus"},{"url":"#home-screen-folders","title":"Home screen folders"},{"url":"#menulink-element","title":"MenuLink element"},{"url":"#homemenulist","title":"HomeMenuList"},{"url":"#genre-view-component","title":"Genre view component"},{"url":"#see-all-link-element","title":"See all link element"},{"url":"#genrelinkview-element","title":"GenreLinkView element"},{"url":"#homesectionheaderview-component","title":"HomeSectionHeaderView component"},{"url":"#homegenrescollection-component","title":"HomeGenresCollection Component"},{"url":"#homegenrescontent-component","title":"HomeGenresContent component"},{"url":"#homescreen-creation","title":"HomeScreen creation"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-02-08T21:20:03.344Z","isNewLayout":true,"previews":{"previews":"{\n    \"Lesson8RemoteImage3\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1612777834/StreamCast/lesson%207/mb4ftefn1oxls86h4pvl.png\" ],\n    \"Lesson8RemoteImage4\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1612777831/StreamCast/lesson%207/dwwmerjeytwmdscmgsom.png\" ]\n}"},"slug":"lesson-8-remoteimage","title":"Lesson #8: RemoteImage","updatedDate":"2021-02-08T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZRJWhXZt58P73bd4VBS8HcUQR1NkF4hStwV","type":"zip","id":"467376b9-f525-5510-92f9-3936130b0b63","name":"Source code"},{"url":"https://www.swiftbysundell.com/articles/caching-in-swift","type":"link","id":"3109b327-2e7b-538e-9c6e-6073291c87aa","name":"Caching in Swift by John Sundell"}],"allChapters":["Lesson8Introdution:Introduction","Lesson8CacheUtils1:Caching utility","Lesson8ImageDownloader1:Image Downloader","Lesson8RemoteImage1:Remote image"],"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson8CacheUtils2\\n// $$liquidcoderFilename-Cache\\n\\nimport Foundation\\n\\nfinal class Cache<Key: Hashable, Value> {\\n    private let wrapped = NSCache<WrappedKey, Entry>()\\n    private let dateProvider: () -> Date\\n    private let entryLifetime: TimeInterval\\n    private let keyTracker = KeyTracker()\\n\\n    subscript(key: Key) -> Value? {\\n        get {\\n            return value(forKey: key)\\n        }\\n\\n        set {\\n            guard let value = newValue else {\\n                remove(forKey: key)\\n                return\\n            }\\n\\n            insert(value, forKey: key)\\n        }\\n    }\\n\\n    init(dateProvider: @escaping () -> Date = Date.init,\\n         entryLifetime: TimeInterval = 12 * 60 * 60, maximumEntryCount: Int = 50) {\\n        self.dateProvider = dateProvider\\n        self.entryLifetime = entryLifetime\\n        wrapped.countLimit = maximumEntryCount\\n        wrapped.delegate = keyTracker\\n    }\\n\\n    func insert(_ value: Value, forKey key: Key) {\\n        let date = dateProvider().addingTimeInterval(entryLifetime)\\n        let entry = Entry(key: key, value: value, expirationDate: date)\\n        insert(entry)\\n    }\\n\\n    func value(forKey key: Key) -> Value? {\\n        entry(forKey: key)?.value\\n    }\\n\\n    func remove(forKey key: Key) {\\n        wrapped.removeObject(forKey: WrappedKey(key))\\n    }\\n}\\n\\nprivate extension Cache {\\n    func entry(forKey key: Key) -> Entry? {\\n        guard let entry = wrapped.object(forKey: WrappedKey(key)) else {\\n            return nil\\n        }\\n\\n        guard dateProvider() < entry.expirationDate else {\\n            remove(forKey: key)\\n            return nil\\n        }\\n\\n        return entry\\n    }\\n\\n    func insert(_ entry: Entry) {\\n        wrapped.setObject(entry, forKey: WrappedKey(entry.key))\\n        keyTracker.keys.insert(entry.key)\\n    }\\n}\\n\\nprivate extension Cache {\\n    final class KeyTracker: NSObject, NSCacheDelegate {\\n        var keys = Set<Key>()\\n\\n        func cache(_ cache: NSCache<AnyObject, AnyObject>,\\n                   willEvictObject object: Any) {\\n            guard let entry = object as? Entry else {\\n                return\\n            }\\n\\n            keys.remove(entry.key)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson8ImageCache1\\n// $$liquidcoderFilename-ImageCache\\n\\nimport Foundation\\n\\n// highlight-start\\nstruct ImageCache {\\n\\n}\\n// highlight-end\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson8ImageCache2\\n// $$liquidcoderFilename-ImageCache\\n\\nimport Foundation\\n\\nstruct ImageCache {\\n\\n    // highlight-start\\n    static var shared = ImageCache()\\n    private let cache: Cache<String, Data>\\n    // highlight-end\\n\\n    // highlight-start\\n    init(lifetime: TimeInterval = 12 * 60 * 60, maximumCount: Int = 200) {\\n\\n        cache = Cache<String, Data>(entryLifetime: lifetime, maximumEntryCount: maximumCount)\\n    }\\n    // highlight-end\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson8ImageCache3\\n// $$liquidcoderFilename-ImageCache\\n\\nimport Foundation\\n\\nstruct ImageCache {\\n\\n    static var shared = ImageCache()\\n    private let cache: Cache<String, Data>\\n\\n    init(lifetime: TimeInterval = 12 * 60 * 60, maximumCount: Int = 200) {\\n\\n        cache = Cache<String, Data>(entryLifetime: lifetime, maximumEntryCount: maximumCount)\\n    }\\n\\n    // highlight-start\\n    func set(_ data: Data, for key: String) {\\n        cache.insert(data, forKey: key as String)\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    func get(for key: String) -> Data? {\\n        cache.value(forKey: key as String)\\n    }\\n    // highlight-end\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson8ImageDownloader1\\n// $$liquidcoderFilename-ImageDownloader\\n\\nimport Foundation\\n// highlight-start\\nimport Combine\\n// highlight-end\\n\\n// highlight-start\\nclass ImageDownloader: ObservableObject {\\n\\n}\\n// highlight-end\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson8ImageDownloader2\\n// $$liquidcoderFilename-ImageDownloader\\n\\nimport Foundation\\nimport Combine\\n\\nclass ImageDownloader: ObservableObject {\\n\\n    // highlight-start\\n    enum State {\\n        case loading\\n        case success(Data)\\n        case failure\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    @Published private(set) var state = State.loading\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson8ImageDownloader3\\n// $$liquidcoderFilename-ImageDownloader\\n\\nimport Foundation\\nimport Combine\\n\\nclass ImageDownloader: ObservableObject {\\n\\n    enum State {\\n        case loading\\n        case success(Data)\\n        case failure\\n    }\\n\\n    @Published private(set) var state = State.loading\\n\\n    // highlight-start\\n    private var subscription: AnyCancellable?\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson8ImageDownloader4\\n// $$liquidcoderFilename-ImageDownloader\\n\\nimport Foundation\\nimport Combine\\n\\nclass ImageDownloader: ObservableObject {\\n\\n    enum State {\\n        case loading\\n        case success(Data)\\n        case failure\\n    }\\n\\n    @Published private(set) var state = State.loading\\n\\n    private var subscription: AnyCancellable?\\n\\n    // highlight-start\\n    private static let queue = DispatchQueue(label: \\\"com.liquidcoder.StreamCast.ImageDownloader\\\")\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson8ImageDownloader5\\n// $$liquidcoderFilename-ImageDownloader\\n\\nimport Foundation\\nimport Combine\\n\\nclass ImageDownloader: ObservableObject {\\n\\n    enum State {\\n        case loading\\n        case success(Data)\\n        case failure\\n    }\\n\\n    @Published private(set) var state = State.loading\\n\\n    private var subscription: AnyCancellable?\\n\\n    private let queue = DispatchQueue(label: \\\"com.liquidcoder.StreamCast.ImageDownloader\\\")\\n\\n    // highlight-start\\n    private let stringUrl: String\\n\\n    init(url: String) {\\n        self.stringUrl = url\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson8ImageDownloader6\\n// $$liquidcoderFilename-ImageDownloader\\n\\nimport Foundation\\nimport Combine\\n\\nclass ImageDownloader: ObservableObject {\\n\\n            ...\\n\\n    // highlight-start\\n    func download() {\\n\\n        if let data = ImageCache.shared.get(for: stringUrl) {\\n            self.state = .success(data)\\n            return\\n        }\\n\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson8ImageDownloader7\\n// $$liquidcoderFilename-ImageDownloader\\n\\nimport Foundation\\nimport Combine\\n\\nclass ImageDownloader: ObservableObject {\\n\\n                ...\\n    func download() {\\n\\n        if let data = ImageCache.shared.get(for: stringUrl) {\\n            self.state = .success(data)\\n            return\\n        }\\n\\n        // highlight-start\\n        guard let url = URL(string: stringUrl) else {\\n            self.state = .failure\\n            return\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson8ImageDownloader8\\n// $$liquidcoderFilename-ImageDownloader\\n\\nimport Foundation\\nimport Combine\\n\\nclass ImageDownloader: ObservableObject {\\n\\n                ...\\n\\n    func download() {\\n\\n                ...\\n\\n        // highlight-start\\n        self.subscription = URLSession.shared.dataTaskPublisher(for: url)\\n            .subscribe(on: self.queue)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson8ImageDownloader9\\n// $$liquidcoderFilename-ImageDownloader\\n\\nimport Foundation\\nimport Combine\\n\\nclass ImageDownloader: ObservableObject {\\n\\n...\\n    func download() {\\n\\n...     \\n\\n        self.subscription = URLSession.shared.dataTaskPublisher(for: url)\\n            .subscribe(on: self.queue)\\n        // highlight-start\\n            .retry(3)\\n            .map { $0.data }\\n            .receive(on: DispatchQueue.main)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson8ImageDownloader10\\n// $$liquidcoderFilename-ImageDownloader\\n\\nimport Foundation\\nimport Combine\\n\\nclass ImageDownloader: ObservableObject {\\n\\n...\\n\\n    func download() {\\n\\n...\\n\\n        self.subscription = URLSession.shared.dataTaskPublisher(for: url)\\n            .subscribe(on: self.queue)\\n            .retry(3)\\n            .map { $0.data }\\n            .receive(on: DispatchQueue.main)\\n        // highlight-start\\n            .sink(receiveCompletion: { [weak self] status in\\n                if case .failure(let error) = status {\\n                    self?.state = .failure\\n                    Log.error(error)\\n                }\\n            }, receiveValue: { [weak self] data in\\n                guard let self = self else { return }\\n                ImageCache.shared.set(data, for: self.stringUrl)\\n                self.state = .success(data)\\n            })\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\n// $$liquidcoderID-Lesson8ImageDownloader11\\n// $$liquidcoderFilename-ImageDownloader\\n\\nimport Foundation\\nimport Combine\\n\\nclass ImageDownloader: ObservableObject {\\n\\n...\\n\\n    func download() {\\n        ...\\n    }\\n\\n    // highlight-start\\n    deinit {\\n        subscription?.cancel()\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson8RemoteImage1\\n// $$liquidcoderFilename-RemoteImage\\n\\nimport SwiftUI\\n\\nstruct RemoteImage: View {\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct RemoteImage_Previews: PreviewProvider {\\n    static var previews: some View {\\n        RemoteImage()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson8RemoteImage2\\n// $$liquidcoderFilename-RemoteImage\\n\\nimport SwiftUI\\n\\n// highlight-start\\nstruct RemoteImage<Loading: View, Failure: View>: View {\\n// highlight-end\\n\\n    // highlight-start\\n    @StateObject private var downloader: ImageDownloader\\n    private let loading: (() -> Loading)\\n    private let failure: (() -> Failure)\\n    // highlight-end\\n\\n    // highlight-start\\n    init(url: String, @ViewBuilder loading: @escaping (() -> Loading), @ViewBuilder failure: @escaping (() -> Failure)) {\\n        _downloader = StateObject(wrappedValue: ImageDownloader(url: url))\\n        self.loading = loading\\n        self.failure = failure\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct RemoteImage_Previews: PreviewProvider {\\n    static var previews: some View {\\n        List {\\n            ForEach(0..<30) { index in\\n                RemoteImage(url: \\\"https://picsum.photos/id/\\\\(index)/200/300\\\", loading: {\\n                    Text(\\\"Loading...\\\")\\n                }, failure: {\\n                    Text(\\\"Failed\\\")\\n                })\\n            }\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson8RemoteImage3\\n// $$liquidcoderFilename-RemoteImage\\n\\nimport SwiftUI\\n\\nstruct RemoteImage<Loading: View, Failure: View>: View {\\n\\n    @StateObject private var downloader: ImageDownloader\\n    private let loading: (() -> Loading)\\n    private let failure: (() -> Failure)\\n\\n    init(url: String, @ViewBuilder loading: @escaping (() -> Loading), @ViewBuilder failure: @escaping (() -> Failure)) {\\n        _downloader = StateObject(wrappedValue: ImageDownloader(url: url))\\n        self.loading = loading\\n        self.failure = failure\\n    }\\n\\n    var body: some View {\\n        // highlight-start\\n        switch downloader.state {\\n        case .loading:\\n            loading()\\n                .onAppear(perform: {\\n                    downloader.download()\\n                })\\n        case .success(let data):\\n            Text(\\\"\\\\(data.count) bytes\\\")\\n        case .failure:\\n            failure()\\n        }\\n        // highlight-end\\n    }\\n}\\n\\n...\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson8RemoteImage4\\n// $$liquidcoderFilename-RemoteImage\\n\\nimport SwiftUI\\n\\nstruct RemoteImage<Loading: View, Failure: View>: View {\\n\\n...\\n\\n    var body: some View {\\n\\n        switch downloader.state {\\n        case .loading:\\n            loading()\\n                .onAppear(perform: {\\n                    downloader.download()\\n                })\\n        case .success(let data):\\n            // highlight-start\\n            createImage(data: data)\\n                .resizable()\\n        // highlight-end\\n        case .failure:\\n            failure()\\n        }\\n\\n    }\\n\\n    // highlight-start\\n    private func createImage(data: Data) -> Image {\\n\\n        if let uiImage = UIImage(data: data) {\\n            return Image(uiImage: uiImage)\\n        }\\n\\n        return Image(systemName: \\\"rectangle.fill\\\")\\n    }\\n    // highlight-end\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson8Introdution-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hello guys, welcome back... This is lesson 8. If you are here, I assume you've already checked the previous lesson in which we created a bunch of views that will compose the home screen. In this one we will create some components that we will use to load images as well as a failure state and a loading views. Let's jump into it.\")), mdx(\"div\", {\n    id: \"Lesson8CacheUtils1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Caching utility\"), mdx(\"p\", null, \"We want to be able cache images when they load from the internet, and the built-in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NSCache\"), \" is much more capable of doing that job. However, a problem arises when the data we want to cache  is a value type, see the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NSCache\"), \" is an objective-C class and can only store class object, so to allow us to save data type like \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Data\"), \" , we will need to create a wrapper around \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NSCache\"), \" that can also save value types.  Fortunately, John from \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://www.swiftbysundell.com/articles/caching-in-swift/#it-all-starts-with-a-declaration\"\n  }), \"swiftbysundell.com\"), \" has written an article on how to do that, so we don't have to create a new one when his implementation works perfectly. I will add the link to the article in the course resources, and I recommend you to read it as well.\")), mdx(\"div\", {\n    id: \"Lesson8CacheUtils2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"So what we are going to do is take what we need from the article, and use it to create our small caching utility struct. Inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Utils\")), \" folder, add a swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Cache.\"))), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS , macOS  and watchOS Extension targets.\")), mdx(\"p\", null, \"So here is the implementation, and I will repeat again: \\\"You should read the article on \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://www.swiftbysundell.com/articles/caching-in-swift/#it-all-starts-with-a-declaration\"\n  }), \"swiftbysundell.com\"), \" \\\".\")), mdx(\"div\", {\n    id: \"Lesson8ImageCache1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now that we have a wrapper around \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NSCache\"), \", it's now safe to create our own little wrapper that we will use to cache data coming from the network.\"), mdx(\"p\", null, \"In the same \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Utils\")), \" folder, add another swift file, and name it \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ImageCache.\"))), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS , macOS  and watchOS Extension targets.\"))), mdx(\"div\", {\n    id: \"Lesson8ImageCache2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We first create a shared instance of the struct which initialises the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ImageCache\"), \" which default values, this means that if the calling site want to customise the lifetime of entries or the maximum entry count, it can do so by creating a new object of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ImageCache\"), \" struct rather than using the shared instance.\"), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cache\"), \" property is an instance of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Cache\"), \" wrapper we have just created, which is obviously generic where the first generic parameter is the type of keys that we will allow to use, and the second is the type of values we will save.\")), mdx(\"div\", {\n    id: \"Lesson8ImageCache3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Last, we create 2 api methods to store and retrieve  data in and from the cache.\")), mdx(\"div\", {\n    id: \"Lesson8ImageDownloader1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Image Downloader\"), mdx(\"p\", null, \"Now that we have our \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Cache\"), \" utility struct, let's now put it to work creating the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ImageDownloader\"), \" class.\"), mdx(\"p\", null, \"Add a swift file in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Networking\")), \" folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ImageDownloader.\"))), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS , macOS  and watchOS Extension targets.\")), mdx(\"p\", null, \"We will need the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Combine\"), \" framework, so we bring it in first. The class conforms to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ObservableObject\"), \" protocol because we will have a property that will be observed by views in the app.\")), mdx(\"div\", {\n    id: \"Lesson8ImageDownloader2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"State\"), \" enum that has  3 cases obviously. We then declare  the class's \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"State\"), \" object property to keep track of the image download state\")), mdx(\"div\", {\n    id: \"Lesson8ImageDownloader3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"subscription\"), \" will hold on to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AnyCancellable\"), \" object returned by the publisher used to fetch the image.\")), mdx(\"div\", {\n    id: \"Lesson8ImageDownloader4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We declare the queue on which to perform the request.\")), mdx(\"div\", {\n    id: \"Lesson8ImageDownloader5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We will also need the image url that we initialise in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"init\"), \" function.\")), mdx(\"div\", {\n    id: \"Lesson8ImageDownloader6-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"download\"), \" function, We first check if we have the data image for the given url in the cache, if so, we set the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" with success containing the data image and return from here, there's no need to perform any further operations.\")), mdx(\"div\", {\n    id: \"Lesson8ImageDownloader7-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We then create a url from the provided url string. If that operation fails, we set the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"failure\"), \", and return ( stopping the operation ).\")), mdx(\"div\", {\n    id: \"Lesson8ImageDownloader8-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"shared\"), \" instance of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"URLSession\"), \", we create the publisher that we subscribe on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"queue\"), \" created above.\")), mdx(\"div\", {\n    id: \"Lesson8ImageDownloader9-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"You already know what the 3 following operators do by now, don't you? We will \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"retry\"), \" 3 times in case of a failure, if all goes well, we retrieve the data from the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Tuple\"), \" result using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"map\"), \" operator, and receive the resulting data on the main queue.\")), mdx(\"div\", {\n    id: \"Lesson8ImageDownloader10-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"sink\"), \" operator, we check if the status is a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"failure\"), \", and set the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" to failure if it's the case, otherwise we move onto second closure, and first save the image to cache before updating the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \"  to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"success\"), \" with the image data passed in.\")), mdx(\"div\", {\n    id: \"Lesson8ImageDownloader11-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The last thing to do is to make sure, we cancel the download operation when the instance is de-allocated before finishing.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"The scenario I am talking about is when a user scrolls  a list  containing images too fast, and images are being de-allocated before they are fully downloaded. In that case, we don't want to continue downloading images that we don't, so we cancel the process as soon as the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ImageDownloader\"), \" instance is de-initialised.\"))), mdx(\"div\", {\n    id: \"Lesson8RemoteImage1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Remote image\"), mdx(\"p\", null, \"We have the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ImageDownloader\"), \" which will take care of fetching  data from a given url, so we need a way of converting that data to what we can use in swiftUI views, which is what we are about to create now.\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI \\u2192 Components\")), \" folder, add a swiftUI file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"RemoteImage\")), \". containing the following code:\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS , macOS  and watchOS Extension targets.\")), mdx(\"p\", null, \"This \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RemoteImage\"), \" is a wrapper around swiftUI's \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Image\"))), mdx(\"div\", {\n    id: \"Lesson8RemoteImage2-Section\",\n    className: \"post-section\"\n  }, mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We first declare the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"downloader\"), \" as Wrapping in the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"StateObject\"), \" to make the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"RemoteView\"), \" the owner of the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"downloader\"), \" because we want its lifespan to be linked to the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"RemoteImage\"), \"'s.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then create 2 properties that we will build a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"loading\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"failure\"), \" view\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"init\"), \", we initialise the properties, but the thing to note here is we initialise the downloader, we don't set the downloader directly rather its wrapped value.\")), mdx(\"hr\", null), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"If you want to understand how this \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RemoteImage\"), \" will be used take a look inside the preview code. We are creating a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"List\"), \" containing a collection of random images downloaded from the internet. We also passing 2 \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" views for both the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"loading\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"failure\"), \" cases respectively.\"))), mdx(\"div\", {\n    id: \"Lesson8RemoteImage3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \", we switch on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"downloader\"), \"'s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \", and return appropriate the loading and failure views for their respective cases, and in the success case we are just returning a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \"  displaying the number of bytes for contained in the data.\"), mdx(\"p\", null, \"Now, resume and run the preview.\")), mdx(\"div\", {\n    id: \"Lesson8RemoteImage4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We create and return the image from the provided data. If we can't create the image, we just return a system rectangle image.\"), mdx(\"p\", null, \"And with this guys, we are done with lesson number 8. See you in lesson 9. Happy coding!!!\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#caching-utility","title":"Caching utility"},{"url":"#image-downloader","title":"Image Downloader"},{"url":"#remote-image","title":"Remote image"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Hello guys, welcome back... This is lesson 8. If you are here, I assume you've already checked the previous lesson in which we created a bunch of views that will compose the home screen. In this one we will create some components that we will use to load images as well as a failure state and a loading views. Let's jump into it.\"), mdx(\"h2\", null, \"Caching utility\"), mdx(\"p\", null, \"We want to be able cache images when they load from the internet, and the built-in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NSCache\"), \" is much more capable of doing that job. However, a problem arises when the data we want to cache  is value types, see the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NSCache\"), \" is an objective-C class and can only store class object, so to allow us to save data type like \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Data\"), \" , we will need to create a wrapper around \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NSCache\"), \" that can also save value types.  Fortunately, John Sundell from \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://www.swiftbysundell.com/articles/caching-in-swift/#it-all-starts-with-a-declaration\"\n  }), \"swiftbysundell.com\"), \" has written an article on how to do that, so we don't have to create a new one when his implementation works perfectly. I will add the link to article in the course resources, and I recommend you to read it as well.\"), mdx(\"p\", null, \"So what we are going to do is take what we need from the article and use it to create our small caching utility struct. Inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Utils\")), \" folder, add a swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Cache.\"))), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS , macOS  and watchOS Extension targets.\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Cache.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\n\\nfinal class Cache<Key: Hashable, Value> {\\n    private let wrapped = NSCache<WrappedKey, Entry>()\\n    private let dateProvider: () -> Date\\n    private let entryLifetime: TimeInterval\\n    private let keyTracker = KeyTracker()\\n\\n    subscript(key: Key) -> Value? {\\n        get {\\n            return value(forKey: key)\\n        }\\n\\n        set {\\n            guard let value = newValue else {\\n                remove(forKey: key)\\n                return\\n            }\\n\\n            insert(value, forKey: key)\\n        }\\n    }\\n\\n    init(dateProvider: @escaping () -> Date = Date.init,\\n         entryLifetime: TimeInterval = 12 * 60 * 60, maximumEntryCount: Int = 50) {\\n        self.dateProvider = dateProvider\\n        self.entryLifetime = entryLifetime\\n        wrapped.countLimit = maximumEntryCount\\n        wrapped.delegate = keyTracker\\n    }\\n\\n    func insert(_ value: Value, forKey key: Key) {\\n        let date = dateProvider().addingTimeInterval(entryLifetime)\\n        let entry = Entry(key: key, value: value, expirationDate: date)\\n        insert(entry)\\n    }\\n\\n    func value(forKey key: Key) -> Value? {\\n        entry(forKey: key)?.value\\n    }\\n\\n    func remove(forKey key: Key) {\\n        wrapped.removeObject(forKey: WrappedKey(key))\\n    }\\n}\\n\\nprivate extension Cache {\\n    func entry(forKey key: Key) -> Entry? {\\n        guard let entry = wrapped.object(forKey: WrappedKey(key)) else {\\n            return nil\\n        }\\n\\n        guard dateProvider() < entry.expirationDate else {\\n            remove(forKey: key)\\n            return nil\\n        }\\n\\n        return entry\\n    }\\n\\n    func insert(_ entry: Entry) {\\n        wrapped.setObject(entry, forKey: WrappedKey(entry.key))\\n        keyTracker.keys.insert(entry.key)\\n    }\\n}\\n\\nprivate extension Cache {\\n    final class KeyTracker: NSObject, NSCacheDelegate {\\n        var keys = Set<Key>()\\n\\n        func cache(_ cache: NSCache<AnyObject, AnyObject>,\\n                   willEvictObject object: Any) {\\n            guard let entry = object as? Entry else {\\n                return\\n            }\\n\\n            keys.remove(entry.key)\\n        }\\n    }\\n}\\n\\nprivate extension Cache {\\n    final class Entry {\\n        let key: Key\\n        let value: Value\\n        let expirationDate: Date\\n\\n        init(key: Key, value: Value, expirationDate: Date) {\\n            self.key = key\\n            self.value = value\\n            self.expirationDate = expirationDate\\n        }\\n    }\\n}\\n\\nprivate extension Cache {\\n    final class WrappedKey: NSObject {\\n        let key: Key\\n\\n        init(_ key: Key) { self.key = key }\\n\\n        override var hash: Int { return key.hashValue }\\n\\n        override func isEqual(_ object: Any?) -> Bool {\\n            guard let value = object as? WrappedKey else {\\n                return false\\n            }\\n\\n            return value.key == key\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"So here is the implementation, and I will repeat again: \\\"You should read the article on \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://www.swiftbysundell.com/articles/caching-in-swift/#it-all-starts-with-a-declaration\"\n  }), \"swiftbysundell.com\"), \" \\\".\"), mdx(\"p\", null, \"Now that we have a wrapper around \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NSCache\"), \", it's now safe to create our own little wrapper that we will use to cache data coming from the network.\"), mdx(\"h2\", null, \"Image cache\"), mdx(\"p\", null, \"In the same \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Utils\")), \" folder, add another swift file, and name it \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ImageCache.\"))), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS , macOS  and watchOS Extension targets.\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ImageCache.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\n\\nstruct ImageCache {\\n\\n    static var shared = ImageCache()\\n    private let cache: Cache<String, Data>\\n\\n    init(lifetime: TimeInterval = 12 * 60 * 60, maximumCount: Int = 200) {\\n        cache = Cache<String, Data>(entryLifetime: lifetime, maximumEntryCount: maximumCount)\\n    }\\n\\n    func set(_ data: Data, for key: String) {\\n        cache.insert(data, forKey: key as String)\\n    }\\n\\n    func get(for key: String) -> Data? {\\n        cache.value(forKey: key as String)\\n    }\\n}\\n\")), mdx(\"p\", null, \"So here is what out little wrapper is doing:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We first create a shared instance of the struct which initilise the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ImageCache\"), \" which default values, this means that if the calling site want to customise the lifetime of entries or the maximum entry count, it can do so by initialising the struct rather than using the shared instance\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Last, we create 2 api methods to store and retrieve  data in and from the cache.\")), mdx(\"h2\", null, \"Image downloader\"), mdx(\"p\", null, \"Add a swift file in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Networking\")), \" folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ImageDownloader.\"))), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS , macOS  and watchOS Extension targets.\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ImageDownloader.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\nimport Combine\\n\\nclass ImageDownloader: ObservableObject {\\n\\n    // 1\\n    enum State {\\n        case loading\\n        case success(Data)\\n        case failure\\n    }\\n\\n    // 2\\n    @Published private(set) var state = State.loading\\n\\n    // 3\\n    private var subscription: AnyCancellable?\\n\\n    // 4\\n    private static let queue = DispatchQueue(label: \\\"com.liquidcoder.StreamCast.ImageDownloader\\\")\\n\\n    // 5\\n    private let stringUrl: String\\n\\n    init(url: String) {\\n        self.stringUrl = url\\n    }\\n\\n    // 6\\n    func download() {\\n\\n        if let data = ImageCache.shared.get(for: stringUrl) {\\n            self.state = .success(data)\\n            return\\n        }\\n\\n        guard let url = URL(string: stringUrl) else {\\n            self.state = .failure\\n            return\\n        }\\n\\n        self.subscription = URLSession.shared.dataTaskPublisher(for: url)\\n            .subscribe(on: Self.queue)\\n            .retry(3)\\n            .map { $0.data }\\n            .receive(on: DispatchQueue.main)\\n            .sink(receiveCompletion: { [weak self] status in\\n                if case .failure(let error) = status {\\n                    self?.state = .failure\\n                    Log.error(error)\\n                }\\n            }, receiveValue: { [weak self] data in\\n                guard let self = self else { return }\\n                ImageCache.shared.set(data, for: self.stringUrl)\\n                self.state = .success(data)\\n            })\\n    }\\n\\n    // 7\\n    func cancel() {\\n        subscription?.cancel()\\n    }\\n\\n    // 8\\n    deinit {\\n        subscription?.cancel()\\n    }\\n}\\n\")), mdx(\"p\", null, \"Here is what the code does:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We create the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"State\"), \" enum that has  3 cases obviously.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We the declare  the class's \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"state\"), \" property to keep track of the image download state\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The subscription will hold on to the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"AnyCancellable\"), \" object returned by the publisher used to fetch the image\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We declare the queue on which to perform the request\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We will also need the image url that we initilise in the init function.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the download function, We first check if we have the data image for the given url in the cache, if so, we set the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"state\"), \" with success containing the data image, otherwise we proceed to fetching the image. We then create the url based on the provided url string. We use the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"URLSession\"), \" \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"dataTaskPublisher\"), \" that we used  for the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ApiService\"), \" to fetch the image data from the internet.  When we get a valid  image data, we save it to cache for the given url before updating the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"state\"), \" with \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \".success\"))), mdx(\"h2\", null, \"Remote image\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI \\u2192 Components\")), \" folder, add a swiftUI file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"RemoteImage\")), \". containing the following code:\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS , macOS  and watchOS Extension targets.\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"RemoteImage.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct RemoteImage<Loading: View, Failure: View>: View {\\n\\n    @StateObject private var downloader: ImageDownloader\\n    private let loading: (() -> Loading)\\n    private let failure: (() -> Failure)\\n\\n    init(url: String, @ViewBuilder loading: @escaping (() -> Loading), @ViewBuilder failure: @escaping (() -> Failure)) {\\n        _downloader = StateObject(wrappedValue: ImageDownloader(url: url))\\n        self.loading = loading\\n        self.failure = failure\\n    }\\n\\n    var body: some View {\\n        switch downloader.state {\\n        case .loading:\\n            loading()\\n                .onAppear(perform: {\\n                    downloader.download()\\n                })\\n        case .success(let data):\\n            createImage(data: data)\\n                .resizable()\\n        case .failure:\\n            failure()\\n        }\\n    }\\n\\n    func createImage(data: Data) -> Image {\\n\\n        if let uiImage = UIImage(data: data) {\\n            return Image(uiImage: uiImage)\\n        }\\n\\n        return Image(systemName: \\\"rectangle.fill\\\")\\n    }\\n}\\n\\nstruct RemoteImage_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        List {\\n            ForEach(0..<30) { index in\\n                RemoteImage(url: \\\"https://picsum.photos/id/\\\\(index)/200/300\\\", loading: {\\n                    Text(\\\"Loading...\\\")\\n                }, failure: {\\n                    Text(\\\"Failed\\\")\\n                })\\n            }\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"This is the wrapper around the swiftUI \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Image\"), \" using our image downloader to download and create an image from the data fetched. Here is what the code does step by step:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We first declare the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"downloader\"), \" as Wrapping in the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"StateObject\"), \" to make the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"RemoteView\"), \" the owner of the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"downloader\"), \" because we want its lifespan to be linked to the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"RemoteImage\"), \"'s.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We then create 2 properties that we will build a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"loading\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"failure\"), \" view\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"init\"), \", we initialise the properties, but the thing to note here is we initialise the downloader, we don't set the downloader directly rather its wrapped value.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"body\"), \", we switch on the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"downloader\"), \"'s \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"state\"), \", and return appropriate views based on the case. \")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Preview\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612777831/StreamCast/lesson%207/dwwmerjeytwmdscmgsom.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1612777831/StreamCast/lesson%207/dwwmerjeytwmdscmgsom.png\"\n  }))), mdx(\"p\", null, \"And with this guys, we are done with lesson number 8. See you in lesson 9. Happy coding!!!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#caching-utility","title":"Caching utility"},{"url":"#image-cache","title":"Image cache"},{"url":"#image-downloader","title":"Image downloader"},{"url":"#remote-image","title":"Remote image"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-02-15T08:01:37.579Z","isNewLayout":true,"previews":{"previews":"{\n    \"Lesson9FailureView2\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613202857/StreamCast/Shared/ffddxuzdwfoxevaugrnd.png\" ],\n    \"Lesson9FailureView3\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613203369/StreamCast/Lesson%209/hg6fuejbys1huauxdlsa.png\" ],\n    \"Lesson9FailureView4\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613203498/StreamCast/Lesson%209/bhmdupxolvkonfs0egox.png\" ],\n    \"Lesson9FailureView5\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613203612/StreamCast/Lesson%209/hg6vsub78ued0gvydkly.png\" ],\n    \"Lesson9FailureView9\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613203887/StreamCast/Lesson%209/xdjcgjwc5qvjk5lronab.png\" ]  \n}"},"slug":"lesson-9-preview-services-and-failureview","title":"Lesson #9: Preview services & FailureView","updatedDate":"2021-02-13T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZqdySXZw4tbLRjI4rLnDFg6C1pidmUNz7jX","type":"zip","id":"bb92517a-7c9e-58f7-953c-71387ddb0922","name":"Source code"},{"url":"https://u.pcloud.link/publink/show?code=XZUdySXZtSI0fom2yKJUOVqaErtrLXj4uxw7","type":"zip","id":"c7c6e88c-0a61-5111-865d-69c2a1da1053","name":"Podcasts.json"}],"allChapters":["Lesson9Introduction:Introduction","Lesson9WhyMock:Why mock classes?","Lesson9PreviewApiService1:Preview API Service","Lesson9PreviewCoredataService1:Preview Coredata Service","Lesson9HomeScreenPreviews1:HomeScreen Previews","Lesson9FailureView1:Failure View"],"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson9PreviewApiService2\\n// $$liquidcoderFilename-PreviewApiService\\n\\nimport Foundation\\n// highlight-start\\nimport Combine\\n// highlight-end\\n\\nclass PreviewApiService: ApiServiceProtocol {\\n\\n    // highlight-start\\n    func remotePublisher(term: String, limit: Int) -> AnyPublisher<Data, URLError> {\\n\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    func localPublisher(filename: String) -> AnyPublisher<Data, URLError> {\\n\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson9PreviewApiService3\\n// $$liquidcoderFilename-PreviewApiService\\n\\nimport Foundation\\nimport Combine\\n\\nclass PreviewApiService: ApiServiceProtocol {\\n\\n    func createPublisher(for filename: String) -> AnyPublisher<Data, URLError> {\\n\\n        // highlight-start\\n        guard let url = Bundle.main.url(forResource: filename, withExtension: \\\"json\\\"),\\n              let data = try? Data(contentsOf: url) else {\\n            return Fail(error: URLError(.badURL) ).eraseToAnyPublisher()\\n        }\\n        // highlight-end\\n    }\\n\\n    func remotePublisher(term: String, limit: Int) -> AnyPublisher<Data, URLError> {\\n\\n    }\\n\\n    func localPublisher(filename: String) -> AnyPublisher<Data, URLError> {\\n\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson9PreviewApiService4\\n// $$liquidcoderFilename-PreviewApiService\\n\\nimport Foundation\\nimport Combine\\n\\nclass PreviewApiService: ApiServiceProtocol {\\n\\n    func createPublisher(for filename: String) -> AnyPublisher<Data, URLError> {\\n\\n        guard let url = Bundle.main.url(forResource: filename, withExtension: \\\"json\\\"),\\n              let data = try? Data(contentsOf: url) else {\\n            return Fail(error: URLError(.badURL) ).eraseToAnyPublisher()\\n        }\\n\\n        // highlight-start\\n        return CurrentValueSubject<Data, URLError>(data)\\n                    .delay(for: 5, scheduler: RunLoop.main)\\n                    .eraseToAnyPublisher()\\n        // highlight-end\\n    }\\n\\n    func remotePublisher(term: String, limit: Int) -> AnyPublisher<Data, URLError> {\\n\\n    }\\n\\n    func localPublisher(filename: String) -> AnyPublisher<Data, URLError> {\\n\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson9PreviewApiService5\\n// $$liquidcoderFilename-PreviewApiService\\n\\nimport Foundation\\nimport Combine\\n\\nclass PreviewApiService: ApiServiceProtocol {\\n\\n    func createPublisher(for filename: String) -> AnyPublisher<Data, URLError> {\\n\\n        guard let url = Bundle.main.url(forResource: filename, withExtension: \\\"json\\\"),\\n              let data = try? Data(contentsOf: url) else {\\n            return Fail(error: URLError(.badURL) ).eraseToAnyPublisher()\\n        }\\n\\n        return CurrentValueSubject<Data, URLError>(data)\\n                    .delay(for: 5, scheduler: RunLoop.main)\\n                    .eraseToAnyPublisher()\\n    }\\n\\n    func remotePublisher(term: String, limit: Int) -> AnyPublisher<Data, URLError> {\\n        // highlight-start\\n        return createPublisher(for: \\\"podcasts\\\")\\n        // highlight-end\\n    }\\n\\n    func localPublisher(filename: String) -> AnyPublisher<Data, URLError> {\\n        // highlight-start\\n        return createPublisher(for: \\\"genres\\\")\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson9PreviewCoredataService1\\n// $$liquidcoderFilename-PreviewCoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass PreviewCoredataService: CoredataServiceProtocol {\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool {\\n        return true\\n    }\\n\\n    func fetchGenres() -> [GenreViewModel] {\\n        return GenreViewModel.listPlaceholders\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson9HomeScreenPreviews11\\n// $$liquidcoderFilename-HomeScreen\\n\\nimport SwiftUI\\n\\nstruct HomeScreen: View {\\n   ...\\n}\\n\\nstruct HomeScreen_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n    // highlight-end\\n\\n    static var previews: some View {\\n        Group {\\n            HomeScreen()\\n                .colorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            HomeScreen()\\n                .previewLayout(.sizeThatFits)\\n            // highlight-start\\n        }.environmentObject(store)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"div\", {\n    id: \"Lesson9FailureView0\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613204463/StreamCast/Lesson%209/sbcebxdpo4gpz1b3xv3v.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613204463/StreamCast/Lesson%209/sbcebxdpo4gpz1b3xv3v.png\"\n  }))))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson9FailureView2\\n// $$liquidcoderFilename-FailureView\\n\\nimport SwiftUI\\n\\nstruct FailureView: View {\\n\\n    // highlight-start\\n    var message = \\\"\\\"\\n    var showIcon = true\\n    var retryAction = {}\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct FailureView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        FailureView()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson9FailureView3\\n// $$liquidcoderFilename-FailureView\\n\\nimport SwiftUI\\n\\nstruct FailureView: View {\\n\\n    var message = \\\"\\\"\\n    var showIcon = true\\n    var retryAction = {}\\n\\n        // highlight-start\\n    var messageView: some View {\\n          VStack(spacing: 10) {\\n              Text(\\\"Oops! something went wrong\\\")\\n                  .font(.title3)\\n                  .bold()\\n\\n              BodyText(value: message)\\n                  .frame(width: 300, alignment: .center)\\n                  .multilineTextAlignment(.center)\\n          }\\n      }\\n        // highlight-end\\n\\n    var body: some View {\\n        // highlight-start\\n        messageView\\n        // highlight-end\\n    }\\n}\\n\\nstruct FailureView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            FailureView(message: \\\"Unable to fetch podcasts, please try again later\\\", showIcon: false)\\n                .previewLayout(.fixed(width: 300, height: 200))\\n                .preferredColorScheme(.dark)\\n            FailureView(message: \\\"Unable to fetch podcasts, please try again later\\\", showIcon: true)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson9FailureView4\\n// $$liquidcoderFilename-FailureView\\n\\nimport SwiftUI\\n\\nstruct FailureView: View {\\n\\n    ...\\n    // highlight-start\\n    var retryButton: some View {\\n        Button(action: retryAction, label: {\\n            Label(\\\"Try again\\\", systemImage: \\\"arrow.counterclockwise\\\")\\n        }).buttonStyle(AccentButtonStyle(verticalPadding: 15) )\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        VStack(spacing: 40) {\\n            messageView\\n            // highlight-start\\n            retryButton\\n            // highlight-end\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson9FailureView5\\n// $$liquidcoderFilename-FailureView\\n\\nimport SwiftUI\\n\\nstruct FailureView: View {\\n\\n    ...\\n\\n    // highlight-start\\n    var icon: some View {\\n        Image(systemName: \\\"exclamationmark.triangle.fill\\\")\\n            .resizable()\\n            .aspectRatio(contentMode: .fit)\\n            .frame(width: 180, height: 180, alignment: .center)\\n            .foregroundColor(Color.pop)\\n    }\\n\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        VStack(spacing: 40) {\\n\\n            // highlight-start\\n            if showIcon {\\n                icon\\n            }\\n            // highlight-end\\n            messageView\\n            retryButton\\n        }\\n    }\\n}\\n\\n    ...\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson9FailureView7\\n// $$liquidcoderFilename-FailureView\\n\\nimport SwiftUI\\n\\nstruct FailureView: View {\\n\\n    ...\\n\\n    var body: some View {\\n\\n        VStack(spacing: 40) {\\n\\n            if showIcon {\\n                icon\\n            }\\n            messageView\\n            retryButton\\n        }\\n    }\\n\\n    // highlight-start\\n    fileprivate func createBackgroundOverlay(mask: String) -> some View {\\n        Rectangle()\\n            .foregroundColor(Color.overlay)\\n            .frame(height: 300)\\n            .mask(\\n                Image(mask)\\n                    .resizable()\\n                    .frame(height: 300))\\n    }\\n    // highlight-end\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson9FailureView9\\n// $$liquidcoderFilename-FailureView\\n\\nimport SwiftUI\\n\\nstruct FailureView: View {\\n\\n    ...\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        ZStack {\\n            // highlight-end\\n\\n            // highlight-start\\n            VStack {\\n                createBackgroundOverlay(mask: \\\"EmptyScreenOverlay1\\\")\\n                Spacer()\\n                createBackgroundOverlay(mask: \\\"EmptyScreenOverlay2\\\")\\n            }.frame(maxHeight: .infinity)\\n            .edgesIgnoringSafeArea(.all)\\n            // highlight-end\\n\\n            VStack(spacing: 40) {\\n\\n                if showIcon {\\n                    icon\\n                }\\n                messageView\\n                retryButton\\n            }\\n\\n            // highlight-start\\n        }\\n        // highlight-end\\n    }\\n\\n    fileprivate func createBackgroundOverlay(mask: String) -> some View {\\n        ...\\n    }\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson9Introduction-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey guys, welcome to lesson number 9... Hope you are enjoying the course. In the previous lesson, we successfully implemented the RemoteImage component. In this one, We are going to create mock classes for the 2 services we currently have. \")), mdx(\"div\", {\n    id: \"Lesson9WhyMock-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Why mock classes?\"), mdx(\"p\", null, \"Remember that we have created a protocol for each service to be able to inject their different implementations  in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppEnvironment\"), \" . So, we are going to add another json file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"podcasts\"), \" containing some sample podcasts in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, mdx(\"strong\", {\n    parentName: \"em\"\n  }, \"Static\")), \" folder, you will see why we need that file shortly. \"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Download the podcasts.json file with this \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://u.pcloud.link/publink/show?code=XZUdySXZtSI0fom2yKJUOVqaErtrLXj4uxw7\"\n  }), \"link\"), \" or you can download the finished lesson #9, and get it from there.\"))), mdx(\"div\", {\n    id: \"Lesson9PreviewApiService1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Preview API Service\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Networking\")), \" folder, add a new swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"PreviewApiService.\"))), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS , macOS and watchOS Extension targets.\"))), mdx(\"div\", {\n    id: \"Lesson9PreviewApiService2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Here is our mock implementation of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiServiceProtocol\"), \", and as required, we need the 2 methods to start. Let's now work on them one by one.\")), mdx(\"div\", {\n    id: \"Lesson9PreviewApiService3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We create a function to be used by the protocol methods.\"), mdx(\"p\", null, \"Here, we are reading a file given its name, and return a publisher containing the data it contains if the operation is successful, otherwise we return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Fail\"), \" one.  This is the same as what we did for reading the genres file in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiService\"), \".\")), mdx(\"div\", {\n    id: \"Lesson9PreviewApiService4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We return a publisher containing the data read from the provided file.\"), mdx(\"p\", null, \"Note the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"delay\"), \" operator added to the publisher before the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"eraseToAnyPublisher\"), \" one, the former one works as its name says, it will delay the process for the amount of seconds provided, this is to simulate a real world scenario where the result takes a little bit longer to return.\")), mdx(\"div\", {\n    id: \"Lesson9PreviewApiService5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Using the function we have just created, we create a publisher for each of the 2 files we have in the project. And with this, we have our mock api service.\")), mdx(\"div\", {\n    id: \"Lesson9PreviewCoredataService1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Preview Coredata Service\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Coredata\")), \" folder, create a new swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PreviewCoredataService\"), \".\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS , macOS and watchOS Extension targets.\")), mdx(\"hr\", null), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PreviewCoredataService\"), \" class is really simple. In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"saveGenres\"), \" method, we just return \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"true\"), \" and the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fetchGenres\"), \" the returns the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreViewModels\"), \" placeholders array.\")), mdx(\"div\", {\n    id: \"Lesson9BeforePreviewsUpdates-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"With those classes done, let us make some modifications in previews where we instantiated the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Store\"), \". \")), mdx(\"div\", {\n    id: \"Lesson9HomeScreenPreviews1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"HomeScreen Previews\"), mdx(\"p\", null, \"The struct to focus on is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeScreen_Previews\"), \" . Add the highlighted lines above \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"previews\"), \" .  Also make sure to remove the existing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"environmentObject\"), \" modifiers.\"), mdx(\"hr\", null), mdx(\"p\", null, \"What we are doing here is using the mock implementation to load previews, so we are injecting them into \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppEnvironment\"), \" instead of using the live ones.  \")), mdx(\"div\", {\n    id: \"Lesson9PreviewsToUpdate1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now, you are also going to change previews in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeGenresContent\"), \" view as well.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Click \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Command + shift + o\")), \"  search for a particular file in the project\"))), mdx(\"div\", {\n    id: \"Lesson9FailureView0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Failure View\"), mdx(\"p\", null, \"This view will be displayed in case of a failure to fetch data from either the database or the network.\")), mdx(\"div\", {\n    id: \"Lesson9FailureView1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"So, in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI \\u2192 Components\")), \" folder, add a new swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"FailureView.swift\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS  targets only.\"))), mdx(\"div\", {\n    id: \"Lesson9FailureView2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We first declare 3 properties that we will use, the first one is obviously the message that will be displayed describing the error , the second one is a boolean to show or hide the error icon, and the last one is a closure that will be used to retry the operation.\")), mdx(\"div\", {\n    id: \"Lesson9FailureView3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"messageView\"), \" which is a simple view composed of 2 \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" views inside a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" that we call in the body block.\"), mdx(\"hr\", null), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Note how we set up the preview. The first instance of the view is using a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fixed\"), \" size layout and the second one is using \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"sizeThatFits\"), \". This is to show you why we need the the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"showIcon\"), \" up there whose job is to hide the icon when the available height is shorter, and to show it when there's enough space.\"))), mdx(\"div\", {\n    id: \"Lesson9FailureView4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next, we create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"retryButton\"), \" passing in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"retryAction\"), \" closure in the button's \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"action\"), \" parameter. \"), mdx(\"p\", null, \"Then, in body we surround the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"messageView\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"retryButton\"), \" with a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" container.\")), mdx(\"div\", {\n    id: \"Lesson9FailureView5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Last, we create the icon which is an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Image\"), \" with a system icon styled properly.\"), mdx(\"hr\", null), mdx(\"p\", null, \"Technically , we are done with this view. Everything we need is there and visible. What we are about to do is totally unnecessary, but it wouldn't kill to make this boring view a bit prettier.\")), mdx(\"div\", {\n    id: \"Lesson9FailureView6-Section\",\n    className: \"post-section\"\n  }, mdx(\"h3\", null, \"Background overlays with masking\"), mdx(\"p\", null, \"We are going to make the background of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"FailureView\"), \" look good with some blob overlays on top and bottom.\")), mdx(\"div\", {\n    id: \"Lesson9FailureView7-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Add the highlighted function below body.\"), mdx(\"hr\", null), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"mask\"), \" modify will mask the view that we apply it to (in our case the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Rectangle\"), \" shape) using the alpha channel of the given view (In our case the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Image\"), \").\")), mdx(\"div\", {\n    id: \"Lesson9FailureView8-Section\",\n    className: \"post-section\"\n  }, mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"An alpha channel is used to define areas of a photo, such as to define transparency or to preserve a saved selection.\"))), mdx(\"div\", {\n    id: \"Lesson9FailureView9-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Last, we embed the existing  and new \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" containers inside a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ZStack\"), \" . The latter will play the role of a background that will display the 2 overlays.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"You will not see any change until you add the 2  png images in the assets catalogue. To find them, download the finished project for this lesson and retrieve them from the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"xcassets\")), \" folder.\"))), mdx(\"div\", {\n    id: \"Lesson9Conclusion1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And with this guys, lesson 9 has come to a close. See you in the tenth lesson. Happy coding!!!\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#why-mock-classes","title":"Why mock classes?"},{"url":"#preview-api-service","title":"Preview API Service"},{"url":"#preview-coredata-service","title":"Preview Coredata Service"},{"url":"#homescreen-previews","title":"HomeScreen Previews"},{"url":"#failure-view","title":"Failure View","items":[{"url":"#background-overlays-with-masking","title":"Background overlays with masking"}]}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Hey guys, welcome to lesson number 9... Hope you are enjoying the course. In the previous lesson, we successfully implemented the RemoteImage component. In this one, We are going to create mock classes for the 2 services we currently have. \"), mdx(\"h2\", null, \"Why mock classes?\"), mdx(\"p\", null, \"Remember that we create a protocol for each service, well like I said we do that in order to be able to inject different implementations of each protocol in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppEnvironment\"), \" . So, we going to    add another json file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"podcasts\")), \" containing some sample podcasts \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"**\"), \"in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Static\")), \" folder, you will see why we need that file shortly.  So, download the file located in the course details sheet.\"), mdx(\"h2\", null, \"Preview API Service\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Networking\")), \" folder, add a new swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"PreviewApiService.\"))), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS , macOS and watchOS Extension targets.\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"PreviewApiService.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\nimport Combine\\n\\nclass PreviewApiService: ApiServiceProtocol {\\n\\n    func remotePublisher(term: String, limit: Int) -> AnyPublisher<Data, URLError> {\\n\\n    }\\n\\n    func localPublisher(filename: String) -> AnyPublisher<Data, URLError> {\\n\\n    }\\n}\\n\")), mdx(\"p\", null, \"Here is our mock implementation of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiServiceProtocol\"), \", and as required we need the 2 methods to start. Let's now work on them one by one.\"), mdx(\"p\", null, \"Add this in \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"MockApiService.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"func createPublisher(for filename: String) -> AnyPublisher<Data, URLError> {\\n\\n        guard let url = Bundle.main.url(forResource: filename, withExtension: \\\"json\\\"),\\n              let data = try? Data(contentsOf: url) else {\\n            return Fail(error: URLError(.badURL) ).eraseToAnyPublisher()\\n        }\\n\\n        return CurrentValueSubject<Data, URLError>(data)\\n            .delay(for: 5, scheduler: RunLoop.main)\\n            .eraseToAnyPublisher()\\n    }\\n\")), mdx(\"p\", null, \"Here, we are reading a file given its name, and return a publisher containing the data it contains if the operation is successful, otherwise we return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Fail\"), \" one.  This is the same as what we did when reading the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"genres.json\"), \" file in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiService\"), \".\"), mdx(\"p\", null, \"Note the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"delay\"), \" operator added to the publisher before the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"eraseToAnyPublisher\"), \" one, the former one works as its name says, it will delay the process for the amount of seconds provided, this is to simulate a real world scenario where the result takes a little bit longer to return.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"MockApiService.swift\"))), mdx(\"p\", null, \"replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"remotePublisher\"), \" method with the following\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"func remotePublisher(term: String, limit: Int) -> AnyPublisher<Data, URLError> {\\n        return createPublisher(for: \\\"podcasts\\\")\\n    }\\n\\n    func localPublisher(filename: String) -> AnyPublisher<Data, URLError> {\\n        return createPublisher(for: \\\"genres\\\")\\n    }\\n\")), mdx(\"p\", null, \"Using the function we have just created, we create a publisher for each of the 2 files we have in the project. And with this, we have our mock api service.\"), mdx(\"h2\", null, mdx(\"strong\", {\n    parentName: \"h2\"\n  }, \"Preview\"), \" Coredata Service\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Coredata\")), \" folder, create a new swift file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"PreviewCoredataService.\"))), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS , macOS and watchOS Extension targets.\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"PreviewCoredataService.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import Foundation\\nimport CoreData\\n\\nclass PreviewCoredataService: CoredataServiceProtocol {\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool {\\n\\n        return true\\n    }\\n\\n    func fetchGenres() -> [GenreViewModel] {\\n        return GenreViewModel.listPlaceholders\\n    }\\n}\\n\")), mdx(\"p\", null, \"The \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"em\"\n  }, \"PreviewCoredataService\"))), \" class is really simple, in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"saveGenres\"), \" method, we just return \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"true\"), \" and the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fetchGenres\"), \" the returns the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreViewModels\"), \" placeholders array.\"), mdx(\"p\", null, \"With those classes done, let us make some modifications in previews where we instantiated the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Store\"), \". \"), mdx(\"h2\", null, \"HomeScreen Previews\"), mdx(\"p\", null, \"The struct to focus on is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeScreen_Previews\"), \" . Add the highlighted lines above \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"previews\"), \" .  Also make sure to remove the existing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"environmentObject\"), \" modifiers.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson9HomeScreenPreviews11\\n// $$liquidcoderFilename-HomeScreen\\n\\nimport SwiftUI\\n\\nstruct HomeScreen: View {\\n   ...\\n}\\n\\nstruct HomeScreen_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n    // highlight-end\\n\\n    static var previews: some View {\\n        Group {\\n            HomeScreen()\\n                .colorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            HomeScreen()\\n                .previewLayout(.sizeThatFits)\\n            // highlight-start\\n        }.environmentObject(store)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"What we are doing here is using the mock implementation to load previews, so we are injecting them into \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppEnvironment\"), \" instead of using the live ones.  \"), mdx(\"p\", null, \"Now, you are also going to change previews in the following views:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"HomeMyLibrarySectionHeader\"))), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"HomeGenresContent\"))), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"HomeMyLibrarySection\"))), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"GenreSelectionScreen\")))), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Click \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Command + shift + o\")), \"  search for a particular file in the project\")), mdx(\"p\", null, \"The above code was taken from this library (\", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://github.com/exyte/ActivityIndicatorView\"\n  }), \"ActivityIndicatorView\"), \").  It's a package containing a collection of activity indicators.\"), mdx(\"h2\", null, \"Failure View\"), mdx(\"p\", null, \"This view will be displayed in case of a failure to fetch data from either the database or the network. So, in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI \\u2192 Components\")), \" folder, add a new swiftUI file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"FailureView.swift\"))), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS  targets only.\")), mdx(\"p\", null, \"Add this in \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"FailureView.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct FailureView: View {\\n\\n// highlight-start\\n    var message = \\\"\\\"\\n    var showIcon = true\\n    var retryAction = {}\\n    // highlight-end\\n\\n}\\n\")), mdx(\"p\", null, \"We first declare 3 properties that we will use, the first one is obviously the message that will be displayed describing the error , the third one is a boolean to show or hide the error icon  and the last one is a closure that will be used to retry the operation.\"), mdx(\"p\", null, \"Add this in \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"FailureView.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"  var messageView: some View {\\n        VStack(spacing: 10) {\\n            Text(\\\"Oops! something went wrong\\\")\\n                .font(.title3)\\n                .bold()\\n\\n            BodyText(value: message)\\n                .frame(width: 300, alignment: .center)\\n                .multilineTextAlignment(.center)\\n        }\\n    }\\n\\n\")), mdx(\"p\", null, \"Here we are creating a message view which is composed of a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" containing 2 \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\")), mdx(\"hr\", null), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Note how we set up the preview. The first instance of the view is using a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fixed\"), \" size layout and the second one is using \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"sizeThatFits\"), \". This is to show you why we need the the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"showIcon\"), \" up there whose job is to hide the icon when the available height is shorter, and to show it when there's space.\")), mdx(\"p\", null, \"Add this in \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"FailureView.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"var icon: some View {\\n        Image(systemName: \\\"exclamationmark.triangle.fill\\\")\\n            .resizable()\\n            .aspectRatio(contentMode: .fit)\\n            .frame(width: 180, height: 180, alignment: .center)\\n            .foregroundColor(Color.pop)\\n    }\\n\\n\")), mdx(\"p\", null, \"This just creates an image using an icon from the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SF Symbols\")), \" library.\"), mdx(\"p\", null, \"Add this in \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"FailureView.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"var retryButton: some View {\\n        Button(action: retryAction, label: {\\n            Label(\\\"Try again\\\", systemImage: \\\"arrow.counterclockwise\\\")\\n        }).buttonStyle(AccentButtonStyle(verticalPadding: 15) )\\n    }\\n\\n\")), mdx(\"p\", null, \"Here we create a button that will be used to retry an operator in case of a failure. We also passing in the closure declared above.\"), mdx(\"p\", null, \"Replace the body in \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"FailureView.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" var body: some View {\\n\\n        ZStack {\\n\\n            VStack {\\n\\n                createBackgroundOverlay(mask: \\\"EmptyScreenOverlay1\\\")\\n                Spacer()\\n                createBackgroundOverlay(mask: \\\"EmptyScreenOverlay2\\\")\\n\\n            }.frame(maxHeight: .infinity)\\n            .edgesIgnoringSafeArea(.all)\\n\\n            VStack(spacing: 40) {\\n                icon\\n                messageView\\n                retryButton\\n            }\\n\\n        }\\n    }\\n\")), mdx(\"p\", null, \"This is where we are putting every bit of code we 've just created in this view. We are missing the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createBackgroundOverlay\"), \" function which is created below.\"), mdx(\"p\", null, \"Add this in \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"FailureView.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"fileprivate func createBackgroundOverlay(mask: String) -> some View {\\n            Rectangle()\\n            .foregroundColor(Color.overlay)\\n            .frame(height: 300)\\n            .mask(\\n                Image(mask)\\n                    .resizable()\\n                    .frame(height: 300))\\n    }\\n\")), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"mask\"), \" modify will mask the view that we apply it to (in our case the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Rectangle\"), \" shape) using the alpha channel of the given view (In our case the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Image\"), \").\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"An alpha channel is used to define areas of a photo, such as to define transparency or to preserve a saved selection.\")), mdx(\"p\", null, \"Preview in \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"FailureView.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\nstruct FailureView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            FailureView(message: \\\"Unable to fetch podcasts, please try again later\\\", showIcon: false)\\n                .previewLayout(.fixed(width: 300, height: 200))\\n                .preferredColorScheme(.dark)\\n            FailureView(message: \\\"Unable to fetch podcasts, please try again later\\\", showIcon: true)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613204463/StreamCast/Lesson%209/sbcebxdpo4gpz1b3xv3v.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613204463/StreamCast/Lesson%209/sbcebxdpo4gpz1b3xv3v.png\"\n  }))));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#why-mock-classes","title":"Why mock classes?"},{"url":"#preview-api-service","title":"Preview API Service"},{"url":"#preview-coredata-service","title":"Preview Coredata Service"},{"url":"#homescreen-previews","title":"HomeScreen Previews"},{"url":"#failure-view","title":"Failure View"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-02-17T09:39:45.179Z","isNewLayout":true,"previews":{"previews":"{\n    \"Lesson10PodcastView1\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613299208/StreamCast/Lesson%2010/txosugbtflfa2577kglv.png\" ],\n    \"Lesson10PodcastView2\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613299208/StreamCast/Lesson%2010/txosugbtflfa2577kglv.png\" ],\n    \"Lesson10PodcastView3\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613299208/StreamCast/Lesson%2010/zejrc3x6p2gxfcdjt4bb.png\" ],\n    \"Lesson10PodcastView4\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613299208/StreamCast/Lesson%2010/ogiej6yrytfdup7zwp0g.png\" ],\n\t\t\"Lesson10PodcastView5\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613299208/StreamCast/Lesson%2010/rxoupky3ngvvjoo1idyt.png\" ],\n\t\t\"Lesson10HomeMyLibrarySectionHeader3\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1613303679/StreamCast/Lesson%2010/k8s9av8srvjpgwyqvcfh.png\"] \n}"},"slug":"lesson-10-home-screen-part-2","title":"Lesson #10: Home Screen Part 2","updatedDate":"2021-02-17T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZtO5SXZJ7mMkLYuEmbPV7rPQkqWeQHs5DYX","type":"zip","id":"9e6cd71f-a3c2-5743-b52e-4703fd89f5d4","name":"Source code"}],"allChapters":["Lesson10Introduction:Introduction","Lesson10SCImage1:StreamCast Image","Lesson10RoundedBorder1:RoundedBorder Modifier","Lesson10PodcastView0:Podcast View","Lesson10MyLibraryGenreList0:MyLibraryGenreList","Lesson10HomeMyLibrarySectionHeader0:HomeMyLibrarySectionHeader"],"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson10SCImage1\\n// $$liquidcoderFilename-SCImage\\n\\nimport SwiftUI\\n\\nstruct SCImage: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct SCImage_Previews: PreviewProvider {\\n    static var previews: some View {\\n        SCImage()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson10SCImage2\\n// $$liquidcoderFilename-SCImage\\n\\nimport SwiftUI\\n\\nstruct SCImage: View {\\n\\n    // highlight-start\\n    var podcast: PodcastViewModel\\n    var size: CGFloat = .zero\\n    var cornerRadius: CGFloat = 10\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct SCImage_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        SCImage(podcast: PodcastViewModel.placeholder)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson10SCImage3\\n// $$liquidcoderFilename-SCImage\\n\\nimport SwiftUI\\n\\nstruct SCImage: View {\\n\\n    var podcast: PodcastViewModel\\n    var size: CGFloat = .zero\\n    var cornerRadius: CGFloat = 10\\n\\n    // highlight-start\\n    var placeholder: some View {\\n        Image(systemName: \\\"squareshape.fill\\\")\\n            .resizable()\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct SCImage_Previews: PreviewProvider {\\n    static var previews: some View {\\n        SCImage(podcast: PodcastViewModel.placeholder)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson10SCImage4\\n// $$liquidcoderFilename-SCImage\\n\\nimport SwiftUI\\n\\nstruct SCImage: View {\\n\\n    var podcast: PodcastViewModel\\n    var size: CGFloat = .zero\\n    var cornerRadius: CGFloat = 10\\n\\n    var placeholder: some View {\\n        Image(systemName: \\\"squareshape.fill\\\")\\n            .resizable()\\n    }\\n\\n    // highlight-start\\n    var failure: some View {\\n            Image(systemName: \\\"xmark\\\")\\n                .resizable()\\n                .aspectRatio(contentMode: .fit)\\n                .frame(width: 30, height: 30)\\n                .foregroundColor(Color.pop)\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct SCImage_Previews: PreviewProvider {\\n    static var previews: some View {\\n        SCImage(podcast: PodcastViewModel.placeholder)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson10SCImage5\\n// $$liquidcoderFilename-SCImage\\n\\nimport SwiftUI\\n\\nstruct SCImage: View {\\n\\n    var podcast: PodcastViewModel\\n    var size: CGFloat = .zero\\n    var cornerRadius: CGFloat = 10\\n\\n    var placeholder: some View {\\n        Image(systemName: \\\"squareshape.fill\\\")\\n            .resizable()\\n    }\\n\\n    var failure: some View {\\n            Image(systemName: \\\"xmark\\\")\\n                .resizable()\\n                .aspectRatio(contentMode: .fit)\\n                .frame(width: 30, height: 30)\\n                .foregroundColor(Color.pop)\\n    }\\n\\n    // highlight-start\\n    var remoteImage: some View {\\n        RemoteImage(url: podcast.imageUrl, loading: {\\n            placeholder\\n                .redacted(reason: .placeholder)\\n        }, failure: {\\n            ZStack {\\n                failure\\n                placeholder\\n                    .redacted(reason: .placeholder)\\n            }\\n        })\\n    }\\n\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct SCImage_Previews: PreviewProvider {\\n    static var previews: some View {\\n        SCImage(podcast: PodcastViewModel.placeholder)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson10SCImage6\\n// $$liquidcoderFilename-SCImage\\n\\nimport SwiftUI\\n\\nstruct SCImage: View {\\n\\n        ...\\n\\n    // highlight-start\\n    var content: some View {\\n        Group {\\n            if let uiImage = UIImage(data: podcast.image) {\\n                Image(uiImage: uiImage)\\n            } else {\\n                remoteImage\\n            }\\n        }\\n    }\\n\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct SCImage_Previews: PreviewProvider {\\n    static var previews: some View {\\n        SCImage(podcast: PodcastViewModel.placeholder)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson10SCImage7\\n// $$liquidcoderFilename-SCImage\\n\\nimport SwiftUI\\n\\nstruct SCImage: View {\\n\\n        ...\\n\\n    var content: some View {\\n        Group {\\n            if let uiImage = UIImage(data: podcast.image) {\\n                Image(uiImage: uiImage)\\n            } else {\\n                remoteImage\\n            }\\n        }\\n    }\\n\\n    var body: some View {\\n        // highlight-start\\n        content\\n            .frame(width: size, height: size, alignment: .center)\\n            .cornerRadius(cornerRadius)\\n            .roundedBorder(radius: cornerRadius, color: Color.border)\\n        // highlight-end\\n    }\\n}\\n\\nstruct SCImage_Previews: PreviewProvider {\\n    static var previews: some View {\\n        SCImage(podcast: PodcastViewModel.placeholder)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson10RoundedBorder2\\n// $$liquidcoderFilename-View+\\n\\nimport Foundation\\n// highlight-start\\nimport SwiftUI\\n// highlight-end\\n\\nextension View {\\n\\n    // highlight-start\\n    func roundedBorder(radius: CGFloat, color: Color) -> some View {\\n        self.overlay(\\n            RoundedRectangle(cornerRadius: radius)\\n                .stroke(color, style: StrokeStyle(lineWidth: 0.5))\\n        )\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"div\", {\n    id: \"Lesson10PodcastView0\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613303304/StreamCast/Lesson%2010/kexzvdazqf0wlbz4x0ll.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613303304/StreamCast/Lesson%2010/kexzvdazqf0wlbz4x0ll.png\"\n  }))))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson10PodcastView1\\n// $$liquidcoderFilename-PodcastView\\n\\nimport SwiftUI\\n\\nstruct PodcastView: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct PodcastView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        PodcastView()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson10PodcastView2\\n// $$liquidcoderFilename-PodcastView\\n\\nimport SwiftUI\\n\\nstruct PodcastView: View {\\n\\n    // highlight-start\\n    var podcast: PodcastViewModel\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct PodcastView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            PodcastView(podcast: PodcastViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PodcastView(podcast: PodcastViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson10PodcastView3\\n// $$liquidcoderFilename-PodcastView\\n\\nimport SwiftUI\\n\\nstruct PodcastView: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    // highlight-start\\n    var details: some View {\\n        VStack(alignment: .leading, spacing: 5) {\\n            Text(podcast.title)\\n                .lineLimit(2)\\n                .font(.headline)\\n                .fixedSize(horizontal: false, vertical: true)\\n                .foregroundColor(Color.text)\\n\\n        }\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        // highlight-start\\n        details\\n        // highlight-end\\n    }\\n}\\n\\nstruct PodcastView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            PodcastView(podcast: PodcastViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PodcastView(podcast: PodcastViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson10PodcastView4\\n// $$liquidcoderFilename-PodcastView\\n\\nimport SwiftUI\\n\\nstruct PodcastView: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    var details: some View {\\n        VStack(alignment: .leading, spacing: 5) {\\n            Text(podcast.title)\\n                .lineLimit(2)\\n                .font(.headline)\\n                .fixedSize(horizontal: false, vertical: true)\\n                .foregroundColor(Color.text)\\n\\n            // highlight-start\\n            Text(podcast.author)\\n                .lineLimit(1)\\n                .font(.subheadline)\\n                .foregroundColor(.gray)\\n\\n            Text(\\\"\\\\(podcast.totalEpisodes) episodes\\\")\\n                .lineLimit(2)\\n                .font(.callout)\\n                .foregroundColor(.gray)\\n\\n            // highlight-end\\n\\n        }\\n    }\\n\\n    var body: some View {\\n        details\\n    }\\n}\\n\\nstruct PodcastView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            PodcastView(podcast: PodcastViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PodcastView(podcast: PodcastViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson10PodcastView5\\n// $$liquidcoderFilename-PodcastView\\n\\nimport SwiftUI\\n\\nstruct PodcastView: View {\\n\\n    var podcast: PodcastViewModel\\n\\n        ...\\n\\n    var body: some View {\\n        // highlight-start\\n        HStack {\\n            SCImage(podcast: podcast, size: 100, cornerRadius: 10)\\n            details\\n        }.frame(maxWidth: .infinity, alignment: .leading)\\n        // highlight-end\\n    }\\n}\\n\\nstruct PodcastView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            PodcastView(podcast: PodcastViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PodcastView(podcast: PodcastViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson10UpdateAccentButtonStyle1\\n// $$liquidcoderFilename-AccentButtonStyle\\n\\nimport Foundation\\nimport SwiftUI\\n\\nstruct AccentButtonStyle: ButtonStyle {\\n\\n    var verticalPadding: CGFloat\\n    // highlight-start\\n    var isActive = false\\n    // highlight-end\\n\\n    func makeBody(configuration: Self.Configuration) -> some View {\\n\\n        let horizontalPadding = verticalPadding * 2\\n\\n        return configuration.label\\n            .padding(.horizontal, horizontalPadding)\\n            // highlight-start\\n            .foregroundColor(isActive ? Color.background : .gray )\\n            .background(isActive ? Color.accentColor : Color.overlay)\\n            // highlight-end\\n            .background(Color.accentColor)\\n            .cornerRadius(15)\\n            .scaleEffect(configuration.isPressed ? 0.9 : 1.0)\\n\\n    }\\n\\n}\\n\")), mdx(\"div\", {\n    id: \"Lesson10MyLibraryGenreList0\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613303304/StreamCast/Lesson%2010/k5uk2rljjmogzv8kehvn.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613303304/StreamCast/Lesson%2010/k5uk2rljjmogzv8kehvn.png\"\n  }))))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson10MyLibraryGenreList1\\n// $$liquidcoderFilename-MyLibraryGenreList\\n\\nimport SwiftUI\\n\\nstruct MyLibraryGenreList: View {\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct MyLibraryGenreList_Previews: PreviewProvider {\\n    static var previews: some View {\\n        MyLibraryGenreList()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson10MyLibraryGenreList2\\n// $$liquidcoderFilename-MyLibraryGenreList\\n\\nimport SwiftUI\\n\\nstruct MyLibraryGenreList: View {\\n\\n    // highlight-start\\n    @Binding var selected: String\\n    // highlight-end\\n\\n    // highlight-start\\n    @EnvironmentObject private var store: Store\\n    @State private var genres: [GenreViewModel] = []\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct MyLibraryGenreList_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n    // highlight-end\\n\\n    static var previews: some View {\\n        Group {\\n            MyLibraryGenreList(selected: .constant(\\\"\\\"))\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            MyLibraryGenreList(selected: .constant(\\\"\\\"))\\n                .previewLayout(.sizeThatFits)\\n            // highlight-start\\n        }.environmentObject(store)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson10MyLibraryGenreList3\\n// $$liquidcoderFilename-MyLibraryGenreList\\n\\nimport SwiftUI\\n\\nstruct MyLibraryGenreList: View {\\n\\n    @Binding var selected: String\\n\\n    @EnvironmentObject private var store: Store\\n    @State private var genres: [GenreViewModel] = []\\n\\n    var body: some View {\\n        // highlight-start\\n        ScrollView(.horizontal, showsIndicators: false, content: {\\n            ScrollViewReader { reader in\\n\\n            }\\n        }).padding(.vertical, 10)\\n        .background(Color.background)\\n        // highlight-end\\n    }\\n}\\n\\nstruct MyLibraryGenreList_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            MyLibraryGenreList(selected: .constant(\\\"\\\"))\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            MyLibraryGenreList(selected: .constant(\\\"\\\"))\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson10MyLibraryGenreList4\\n// $$liquidcoderFilename-MyLibraryGenreList\\n\\nimport SwiftUI\\n\\nstruct MyLibraryGenreList: View {\\n\\n    @Binding var selected: String\\n\\n    @EnvironmentObject private var store: Store\\n    @State private var genres: [GenreViewModel] = []\\n\\n    var body: some View {\\n        ScrollView(.horizontal, showsIndicators: false, content: {\\n            ScrollViewReader { reader in\\n                // highlight-start\\n                createScrollViewContent(reader: reader)\\n                // highlight-end\\n            }\\n        }).padding(.vertical, 10)\\n        .background(Color.background)\\n    }\\n\\n    // highlight-start\\n    fileprivate func createScrollViewContent(reader: ScrollViewProxy) -> some View {\\n        HStack(spacing: 10) {\\n            ForEach(0..<genres.count, id: \\\\.self) { index in\\n\\n            }\\n        }.padding(.leading)\\n    }\\n    // highlight-end\\n}\\n\\nstruct MyLibraryGenreList_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            MyLibraryGenreList(selected: .constant(\\\"\\\"))\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            MyLibraryGenreList(selected: .constant(\\\"\\\"))\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson10MyLibraryGenreList5\\n// $$liquidcoderFilename-MyLibraryGenreList\\n\\nimport SwiftUI\\n\\nstruct MyLibraryGenreList: View {\\n\\n    @Binding var selected: String\\n\\n    @EnvironmentObject private var store: Store\\n    @State private var genres: [GenreViewModel] = []\\n\\n    var body: some View {\\n        ...\\n    }\\n\\n    fileprivate func createScrollViewContent(reader: ScrollViewProxy) -> some View {\\n        HStack(spacing: 10) {\\n            ForEach(0..<genres.count, id: \\\\.self) { index in\\n                // highlight-start\\n                createButton(index, reader: reader)\\n                // highlight-end\\n            }\\n        }.padding(.leading)\\n    }\\n\\n    // highlight-start\\n    fileprivate func createButton(_ index: Int, reader: ScrollViewProxy) -> some View {\\n\\n        let isSelected = genres[index].name == selected\\n\\n        return Button(action: {\\n            selected = genres[index].name\\n            reader.scrollTo(index)\\n        }, label: {\\n            Text(genres[index].name)\\n                .multilineTextAlignment(.leading)\\n        }).buttonStyle(AccentButtonStyle(verticalPadding: 10, isActive: isSelected) )\\n        .tag(index)\\n\\n    }\\n    // highlight-end\\n}\\n\\nstruct MyLibraryGenreList_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            MyLibraryGenreList(selected: .constant(\\\"\\\"))\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            MyLibraryGenreList(selected: .constant(\\\"\\\"))\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson10MyLibraryGenreList6\\n// $$liquidcoderFilename-MyLibraryGenreList\\n\\nimport SwiftUI\\n\\nstruct MyLibraryGenreList: View {\\n\\n    @Binding var selected: String\\n\\n    @EnvironmentObject private var store: Store\\n    @State private var genres: [GenreViewModel] = []\\n\\n    var body: some View {\\n        ScrollView(.horizontal, showsIndicators: false, content: {\\n            ScrollViewReader { reader in\\n                createScrollViewContent(reader: reader)\\n            }\\n        }).padding(.vertical, 10)\\n        .background(Color.background)\\n        // highlight-start\\n        .onAppear {\\n            store.dispatch(.coredata(.fetchGenres))\\n        }.onChange(of: store.coredataState.savedGenres, perform: { value in\\n            genres = value\\n            if selected.isEmpty {\\n                selected = value[0].name\\n            }\\n        })\\n        // highlight-end\\n    }\\n\\n    fileprivate func createScrollViewContent(reader: ScrollViewProxy) -> some View {\\n        HStack(spacing: 10) {\\n            ForEach(0..<genres.count, id: \\\\.self) { index in\\n                createButton(index, reader: reader)\\n            }\\n        }.padding(.leading)\\n    }\\n\\n    fileprivate func createButton(_ index: Int, reader: ScrollViewProxy) -> some View {\\n\\n        let isSelected = genres[index].name == selected\\n\\n        return Button(action: {\\n            selected = genres[index].name\\n            reader.scrollTo(index)\\n        }, label: {\\n            Text(genres[index].name)\\n                .multilineTextAlignment(.leading)\\n        }).buttonStyle(AccentButtonStyle(verticalPadding: 10, isActive: isSelected) )\\n        .tag(index)\\n\\n    }\\n}\\n\\nstruct MyLibraryGenreList_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            MyLibraryGenreList(selected: .constant(\\\"\\\"))\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            MyLibraryGenreList(selected: .constant(\\\"\\\"))\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"div\", {\n    id: \"Lesson10HomeMyLibrarySectionHeader0\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613303304/StreamCast/Lesson%2010/zlndcmcvhy0t1dawqzao.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613303304/StreamCast/Lesson%2010/zlndcmcvhy0t1dawqzao.png\"\n  }))))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson10HomeMyLibrarySectionHeader1\\n// $$liquidcoderFilename-HomeMyLibrarySectionHeader\\n\\nimport SwiftUI\\n\\nstruct HomeMyLibrarySectionHeader: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct HomeMyLibrarySectionHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        HomeMyLibrarySectionHeader()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson10HomeMyLibrarySectionHeader2\\n// $$liquidcoderFilename-HomeMyLibrarySectionHeader\\n\\nimport SwiftUI\\n\\nstruct HomeMyLibrarySectionHeader: View {\\n\\n    // highlight-start\\n    @Binding var selected: String\\n    // highlight-end\\n\\n    var body: some View {\\n        // highlight-start\\n        MyLibraryGenreList(selected: $selected)\\n        // highlight-end\\n    }\\n}\\n\\nstruct HomeMyLibrarySectionHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            HomeMyLibrarySectionHeader(selected: .constant(\\\"\\\"))\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            HomeMyLibrarySectionHeader(selected: .constant(\\\"\\\"))\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-start\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson10HomeMyLibrarySectionHeader3\\n// $$liquidcoderFilename-HomeMyLibrarySectionHeader\\n\\nimport SwiftUI\\n\\nstruct HomeMyLibrarySectionHeader: View {\\n\\n    @Binding var selected: String\\n\\n    var body: some View {\\n        HStack {\\n            MyLibraryGenreList(selected: $selected)\\n\\n            // highlight-start\\n            SeeAllLink(destination: {\\n                Text(\\\"My library\\\")\\n            }).padding()\\n            // highlight-end\\n\\n            // highlight-start\\n        }.padding(.vertical, 10)\\n        .background(Color.background)\\n        // highlight-end\\n    }\\n}\\n\\nstruct HomeMyLibrarySectionHeader_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n    // highlight-end\\n\\n    static var previews: some View {\\n        Group {\\n            HomeMyLibrarySectionHeader(selected: .constant(\\\"\\\"))\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            HomeMyLibrarySectionHeader(selected: .constant(\\\"\\\"))\\n                .previewLayout(.sizeThatFits)\\n            // highlight-start\\n        }.environmentObject(store)\\n        // highlight-end\\n    }\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson10Introduction-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey welcome to lesson number #10. In the previous one, we took a break from the main flow to create preview services and a failure view. In this one, we will continue building the home screen.\")), mdx(\"div\", {\n    id: \"Lesson10SCImage1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"StreamCast Image\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI \\u2192 Elements\")), \" folder, create a new swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"SCImage\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only for now.\"))), mdx(\"div\", {\n    id: \"Lesson10SCImage2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We first declare 3 properties that will be exposed to the calling view and can also be customised from there.\")), mdx(\"div\", {\n    id: \"Lesson10SCImage3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next, we create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"placeholder\"), \" view, which in our case, is just a simple image, but you can replace that with any type of view.\")), mdx(\"div\", {\n    id: \"Lesson10SCImage4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next-up, we have the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"failure\"), \" view which will be displayed when there's an error fetching the image, and just like the placeholder, this can also be any type of view.\")), mdx(\"div\", {\n    id: \"Lesson10SCImage5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next, we create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"remoteImage\"), \" view which is of type \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RemoteImage\"), \" passing in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"loading\"), \" view in the loading block, and in the failure block we add an \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"xmark\"), \" on top of the placeholder image. You are totally free to experiment with other views.\")), mdx(\"div\", {\n    id: \"Lesson10SCImage6-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"remoteImage\"), \",  we create the content that will be called in the body.\"), mdx(\"hr\", null), mdx(\"p\", null, \"The podcast view model has a field called image which is of type \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Data\"), \", initially this field will be empty, it will only be valid for saved podcasts. So, in the body we first check  whether the image is valid, if it is, then there is no need to fetch it from the internet. If it's not valid, we return the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"remoteImage\"), \" that will fetch the image from the internet.\")), mdx(\"div\", {\n    id: \"Lesson10SCImage7-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Last we call the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"content\"), \" inside the body, giving it a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"frame\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cornerRadius\"), \" and a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"roundedBorder\"), \", the latter being a custom modifier that does not exist yet, so let's create that next.\")), mdx(\"div\", {\n    id: \"Lesson10RoundedBorder1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"RoundedBorder Modifier\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ShareUI \\u2192 Modifiers\")), \" folder, add a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"View+\"), \" \"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS, macOS and watchOS targets.\"))), mdx(\"div\", {\n    id: \"Lesson10RoundedBorder2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"This is an extension on the View protocol which create a modifier to apply a rounded border with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"0.5\"), \" of line width.\")), mdx(\"div\", {\n    id: \"Lesson10PodcastView0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Podcast View\"), mdx(\"p\", null, \"This is what we will build in this section. \"), mdx(\"p\", null, \"It's a single cell in the list of podcast as you can see  in the picture on the right.\")), mdx(\"div\", {\n    id: \"Lesson10PodcastView1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI \\u2192 Components\")), \", create a new swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PodcastView\"), \". \"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only for now.\")), mdx(\"hr\", null), mdx(\"p\", null, \"This component will be used by multiple views in different screen that's why we are putting it in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ShareUI\")), \"  folder.\")), mdx(\"div\", {\n    id: \"Lesson10PodcastView2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We are creating a podcast view, hence the need of a podcast model.\")), mdx(\"div\", {\n    id: \"Lesson10PodcastView3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next we begin creating the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"details\"), \" view starting with the title. \"), mdx(\"hr\", null), mdx(\"p\", null, mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"limit(2)\"), \":  The title will be limited to 2 lines\"), mdx(\"p\", null, mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fixedSize\"), \": The title will fit as many characters vertically as possible, but limited to only 2 lines.\")), mdx(\"div\", {\n    id: \"Lesson10PodcastView4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next, we add the author and total episodes count text views.\")), mdx(\"div\", {\n    id: \"Lesson10PodcastView5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the body, we put the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SCImage\"), \" and the details inside a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HStack\"), \" \")), mdx(\"div\", {\n    id: \"Lesson10UpdateAccentButtonStyle1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We need to make some modification to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AccentButtonStyle\"), \".\"), mdx(\"hr\", null), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We have introduced a new property with a default value of false indicating that the button to which this style will be applied to will initially be inactive.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The foreground and background colors are both dynamic based on the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"isActive\"), \" value.\"))), mdx(\"div\", {\n    id: \"Lesson10MyLibraryGenreList0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"MyLibraryGenreList\"), mdx(\"p\", null, \"This is what we are will build in this section. It's part of the MyLibrarySectionHeader (The last section on the home screen). \")), mdx(\"div\", {\n    id: \"Lesson10MyLibraryGenreList1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI \\u2192 Components ,\")), \" create a new swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"MyLibraryGenreList\"), \".\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets.\"))), mdx(\"div\", {\n    id: \"Lesson10MyLibraryGenreList2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The binding \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"selected\"), \"  property will be set whenever a genre is selected, we also need the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"store\"), \" to fetch saved genres with, and genres array will hold fetched genres from the database.\"), mdx(\"hr\", null), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Remember in the preview lesson, we implemented preview services for all the services we currently have. So, having declare the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"store\"), \" object in the view, we also need to attach the preview implementation in preview provider.\"))), mdx(\"div\", {\n    id: \"Lesson10MyLibraryGenreList3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the  body block, we create a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Scrollview\"), \", and inside it we wrap the content in a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ScrollviewReader\"), \" because we want to scroll to the selected genre and focus on it.\")), mdx(\"div\", {\n    id: \"Lesson10MyLibraryGenreList4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We then create the scrollView content that we call inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ScrollviewReader\"), \" block.\"), mdx(\"p\", null, \"The content of the Scrollview is an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HStack\"), \" containing a list of genres used as button label each.  \")), mdx(\"div\", {\n    id: \"Lesson10MyLibraryGenreList5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createButton\"), \" function will create a button containing the selected genre name as label content.\")), mdx(\"div\", {\n    id: \"Lesson10MyLibraryGenreList6-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onAppear\"), \", we dispatch a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"coredata\"), \" action to fetch saved genres, then on attach the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onChange\"), \" modifier to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ScrollView\"), \" to listen on when the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"savedGenres\"), \" state property changes, when that happens we want not only to set the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"genres\"), \" array property, but also to set the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"selected\"), \" one with the first element in array\")), mdx(\"div\", {\n    id: \"Lesson10MyLibraryGenreList7-Section\",\n    className: \"post-section\"\n  }, mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Don't worry about the preview not showing anything for this view, I am going to fix it later, will update the section.\"))), mdx(\"div\", {\n    id: \"Lesson10HomeMyLibrarySectionHeader0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"HomeMyLibrarySectionHeader\"), mdx(\"p\", null, \"This is what we are will build in this section. As you can see on the right, it's composed of the saved genres list and the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"SeeAllLink.\"))), mdx(\"div\", {\n    id: \"Lesson10HomeMyLibrarySectionHeader1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Home \\u2192 Components\")), \" , create a new swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"HomeMyLibrarySectionHeader\"), \".\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\"))), mdx(\"div\", {\n    id: \"Lesson10HomeMyLibrarySectionHeader2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Here we declare a selected property that we bind to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"selected\"), \" binding property  in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"MyLibraryGenreList\"), \" that we've just created.\")), mdx(\"div\", {\n    id: \"Lesson10HomeMyLibrarySectionHeader3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Last, we put the existing view and the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"seeAllLink\"), \" inside an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HStack\"), \" that.\"), mdx(\"hr\", null), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"We need to add the preview store in the preview provider for it to work.\"))), mdx(\"div\", {\n    id: \"Lesson10Outro1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"This brings lesson #10 to a close. Let's jump to #11. Happy coding!\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#streamcast-image","title":"StreamCast Image"},{"url":"#roundedborder-modifier","title":"RoundedBorder Modifier"},{"url":"#podcast-view","title":"Podcast View"},{"url":"#mylibrarygenrelist","title":"MyLibraryGenreList"},{"url":"#homemylibrarysectionheader","title":"HomeMyLibrarySectionHeader"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Hey welcome to lesson number #10. In the previous one, we took a break from the main flow to create preview services and a failure view. In this one, we will continue building the home screen.\"), mdx(\"h2\", null, \"StreamCast Image\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI \\u2192 Elements\")), \" folder, create a new swiftUI file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SCImage.\"))), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only for now.\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SCImage.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct SCImage: View {\\n\\n    var podcast: PodcastViewModel\\n    var size: CGFloat = .zero\\n    var cornerRadius: CGFloat = 10\\n\\n    private var placeholder: some View {\\n        Image(systemName: \\\"squareshape.fill\\\")\\n            .resizable()\\n    }\\n\\n    private var failure: some View {\\n            Image(systemName: \\\"xmark\\\")\\n                .resizable()\\n                .aspectRatio(contentMode: .fit)\\n                .frame(width: 30, height: 30)\\n                .foregroundColor(Color.pop)\\n    }\\n\\n    private var remoteImage: some View {\\n        RemoteImage(url: podcast.imageUrl, loading: {\\n            placeholder\\n                .redacted(reason: .placeholder)\\n        }, failure: {\\n            ZStack {\\n                failure\\n                placeholder\\n                    .redacted(reason: .placeholder)\\n            }\\n        })\\n    }\\n\\n    var body: some View {\\n\\n        Group {\\n            if let uiImage = UIImage(data: podcast.image) {\\n                Image(uiImage: uiImage)\\n            } else {\\n                remoteImage\\n            }\\n        }\\n        .frame(width: size, height: size, alignment: .center)\\n        .cornerRadius(cornerRadius)\\n        .roundedBorder(radius: cornerRadius, color: Color.border)\\n    }\\n}\\n\\nstruct SCImage_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            SCImage(podcast: PodcastViewModel.placeholder, size: 300)\\n                .previewLayout(.sizeThatFits)\\n            SCImage(podcast: PodcastViewModel.placeholder, size: 300)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n        }\\n\\n    }\\n}\\n\")), mdx(\"p\", null, \"Here is what this code does:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"We first declare 3 properties that will be exposed and set by the calling site\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Next, we create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"placeholder\"), \" view, which in our case, is just a simple image. However, you replace that with any type of view.\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Next-up, we have the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"failure\"), \" view which will be displayed when there's an error fetching the image, and just like the placeholder, this can be any type of view.\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Next, we create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"remoteImage\"), \" view which is of type \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RemoteImage\"), \" passing in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"loading\"), \" view in the loading block, and in the failure block we add an \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"xmark\"), \" on top of the placeholder image, you are totally free to experiment with other views.\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"The podcast view model has a field called image which is of type \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Data\"), \", initially this field will be empty, it will only be valid for saved podcasts. So, in the body we first check  whether the image is valid, if it is there is no need to fetch it from the internet. If it's not valid, we return the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"remoteImage\"), \" which will fetch the image from the internet.\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Last we call the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"content\"), \" inside the body, giving it a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"frame\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cornerRadius\"), \" and a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"roundedBorder\"), \" which is a custom modifier which explains the error you are seeing right now, let's create it next.\"))), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ShareUI\")), \" folder, add another folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Modifiers,\")), \" and put a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"View+\"), \" containing the following code:\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS, macOS and watchOS targets.\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"View+.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"extension View {\\n\\n    func roundedBorder(radius: CGFloat, color: Color) -> some View {\\n        self.overlay(\\n            RoundedRectangle(cornerRadius: radius)\\n                .stroke(color, style: StrokeStyle(lineWidth: 0.5))\\n        )\\n    }\\n\\n}\\n\")), mdx(\"p\", null, \"This is an extension on View protocol which create a modifier to apply a rounded border with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"0.5\"), \" of line width.\"), mdx(\"h2\", null, \"Podcast View\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI \\u2192 Components ,\")), \" create a new swiftUI file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"PodcastView.\")), \" This component will be used by multiple views in different screen that's why we are putting it in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ShareUI\")), \"  folder.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only for now.\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"PodcastView.swift\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct PodcastView: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    var details: some View {\\n        VStack(alignment: .leading, spacing: 5) {\\n            Text(podcast.title)\\n                .lineLimit(2)\\n                .font(.headline)\\n                .fixedSize(horizontal: false, vertical: true)\\n                .foregroundColor(Color.text)\\n\\n            Text(podcast.author)\\n                .lineLimit(1)\\n                .font(.subheadline)\\n                .foregroundColor(.gray)\\n\\n            Text(\\\"\\\\(podcast.totalEpisodes) episodes\\\")\\n                .lineLimit(2)\\n                .font(.callout)\\n                .foregroundColor(.gray)\\n        }\\n    }\\n\\n    var body: some View {\\n        HStack {\\n            SCImageView(podcast: podcast, size: 100, cornerRadius: 10)\\n            details\\n        }.frame(maxWidth: .infinity, alignment: .leading)\\n    }\\n}\\n\\nstruct PodcastView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            PodcastView(podcast: .placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n\\n            PodcastView(podcast: .placeholder)\\n                .previewLayout(.sizeThatFits)\\n\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"Here is a breakdown of what we are doing here. \"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We first create the details view which is a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"VStack\"), \", containing 3 views inside.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the body, we put the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"SCImage\"), \" and the details inside a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"HStack\"), \" \")), mdx(\"h2\", null, \"Update to the \", mdx(\"inlineCode\", {\n    parentName: \"h2\"\n  }, \"AccentButtonStyle\")), mdx(\"p\", null, \"Replace the content of the struct with the following\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"var verticalPadding: CGFloat\\n    var isActive = false\\n\\n    func makeBody(configuration: Self.Configuration) -> some View {\\n\\n        let horizontalPadding = verticalPadding * 2\\n\\n        return configuration.label\\n            .padding(.horizontal, horizontalPadding)\\n            .padding(.vertical, verticalPadding)\\n            .foregroundColor(isActive ? Color.background : .gray )\\n            .background(isActive ? Color.accentColor : Color.overlay)\\n            .cornerRadius(15)\\n            .scaleEffect(configuration.isPressed ? 0.9 : 1.0)\\n    }\\n\")), mdx(\"p\", null, \"Here is what we've change above.\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We have introduced a new property with a default value of false indicating that the button to which this style will be applied to will initially be inactive.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The foreground and background colors are both dynamic based on the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"isActive\"), \" value.\")), mdx(\"h2\", null, \"MyLibraryGenreList\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI \\u2192 Components ,\")), \" create a new swiftUI file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"MyLibraryGenreList.\"))), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct MyLibraryGenreList: View {\\n\\n    @Binding var selected: String\\n\\n    @EnvironmentObject private var store: Store\\n    @State private var genres: [GenreViewModel] = []\\n\\n        var body: some View {\\n        ScrollView(.horizontal, showsIndicators: false, content: {\\n            ScrollViewReader { reader in\\n                createScrollViewContent(reader: reader)\\n            }\\n        }).padding(.vertical, 10)\\n        .background(Color.background)\\n        .onAppear {\\n            store.dispatch(.coredata(.fetchGenres))\\n        }.onChange(of: store.coredataState.savedGenres, perform: { value in\\n            genres = value\\n            if selected.isEmpty {\\n                selected = value[0].name\\n            }\\n        })\\n    }\\n\\n    fileprivate func createScrollViewContent(reader: ScrollViewProxy) -> some View {\\n        HStack(spacing: 10) {\\n            ForEach(0..<genres.count, id: \\\\.self) { index in\\n                createButton(index, reader: reader)\\n            }\\n        }.padding(.leading)\\n    }\\n\\n    fileprivate func createButton(_ index: Int, reader: ScrollViewProxy) -> some View {\\n\\n        let isSelected = genres[index].name == selected\\n\\n        return Button(action: {\\n            selected = genres[index].name\\n            reader.scrollTo(index)\\n        }, label: {\\n            Text(genres[index].name)\\n                .multilineTextAlignment(.leading)\\n        }).buttonStyle(AccentButtonStyle(verticalPadding: 10, isActive: isSelected) )\\n        .tag(index)\\n\\n    }\\n}\\n\\nstruct MyLibraryHeaderView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            MyLibraryGenreList(selected: .constant(\\\"\\\"))\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n                .environmentObject(Store())\\n            MyLibraryGenreList(selected: .constant(\\\"\\\"))\\n                .previewLayout(.sizeThatFits)\\n                .environmentObject(Store())\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"Here is a breakdown of what the above code does;\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The binding \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"selected\"), \"  property will be set whenever a genre is selected, we also need the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"store\"), \" to fetch saved genres with, and genres array will hold fetched saved genres.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onAppear\"), \", we dispatch a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"coredata\"), \" action to fetch saved genres, then on attach the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onChange\"), \" modifier to the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ScrollView\"), \" to listen on when the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"savedGenres\"), \" state property changes, when that happens we want not only to set the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"genres\"), \" array property, but also to set the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"selected\"), \" one with the first element in array\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the  body block, we create a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Scrollview\"), \", and inside it we wrap the content inside \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ScrollviewReader\"), \" because we want to scroll to the selected genre and focus on it.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The content of the Scrollview is an \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"HStack\"), \" containing a list of buttons. \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"createButton\"), \" function will create a button containing the selected genre name as label content. \")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Don't worry about the preview not showing anything for this view, I am going to fix it later, will update the section.\")), mdx(\"h2\", null, \"HomeMyLibrarySectionHeader\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Home \\u2192 Components ,\")), \" create a new swiftUI file named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"HomeMyLibrarySectionHeader.\"))), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct HomeMyLibrarySectionHeader: View {\\n\\n    @Binding var selected: String\\n\\n    var body: some View {\\n        HStack {\\n\\n            MyLibraryGenreList(selected: $selected)\\n\\n            SeeAllLink(destination: {\\n                Text(\\\"My library\\\")\\n            }).padding()\\n            .background( Color.background )\\n        }.padding(.vertical, 10)\\n        .background(Color.background)\\n    }\\n}\\n\\nstruct HomeMyLibrarySectionHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            HomeMyLibrarySectionHeader(selected: .constant(\\\"\\\"))\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n                .environmentObject(Store())\\n            HomeMyLibrarySectionHeader(selected: .constant(\\\"\\\"))\\n                .previewLayout(.sizeThatFits)\\n                .environmentObject(Store())\\n        }\\n\\n    }\\n}\\n\")), mdx(\"p\", null, \"Here, we put the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"MyLibraryGenreList\"), \" we've just created with the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SeeAllLiink\"), \" together in a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HStack\"), \".\"), mdx(\"p\", null, \"And with this we are done with lesson #10, let's jump to #11. Happy coding.\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#streamcast-image","title":"StreamCast Image"},{"url":"#podcast-view","title":"Podcast View"},{"url":"#update-to-the-accentbuttonstyle","title":"Update to the AccentButtonStyle"},{"url":"#mylibrarygenrelist","title":"MyLibraryGenreList"},{"url":"#homemylibrarysectionheader","title":"HomeMyLibrarySectionHeader"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-02-22T12:13:59.034Z","isNewLayout":true,"previews":{"previews":"{\n    \"Lesson11RedactedListView1\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613202857/StreamCast/Shared/ffddxuzdwfoxevaugrnd.png\" ],\n    \"Lesson11RedactedListView2\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613202857/StreamCast/Shared/ffddxuzdwfoxevaugrnd.png\" ],\n    \"Lesson11RedactedListView3\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613920751/StreamCast/Lesson%2011/fyvnzrrdjkqysoxfk0ij.png\" ],\n    \"Lesson11PodcastLinkView1\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613202857/StreamCast/Shared/ffddxuzdwfoxevaugrnd.png\" ],\n    \"Lesson11PodcastLinkView2\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613920901/StreamCast/Lesson%2011/yt9nfiaszpm6xxzqican.png\" ],\n    \"Lesson11PodcastListContent1\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613202857/StreamCast/Shared/ffddxuzdwfoxevaugrnd.png\" ],\n    \"Lesson11PodcastListContent2\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613202857/StreamCast/Shared/ffddxuzdwfoxevaugrnd.png\" ],\n    \"Lesson11PodcastListContent3\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613921269/StreamCast/Lesson%2011/lu1b00zhoxazpud0cf06.png\" ],\n    \"Lesson11PodcastListContent4\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613921104/StreamCast/Lesson%2011/hh3yruhimqaaikkvew11.png\" ],\n    \"Lesson11PodcastListContent5\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613921476/StreamCast/Lesson%2011/aynl3markrrps2wqlrmg.png\" ],\n    \"Lesson11PodcastListContent6\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613203887/StreamCast/Lesson%209/xdjcgjwc5qvjk5lronab.png\" ],\n    \"Lesson11HomeMyLibrarySection1\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613202857/StreamCast/Shared/ffddxuzdwfoxevaugrnd.png\" ],\n    \"Lesson11HomeMyLibrarySection2\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613202857/StreamCast/Shared/ffddxuzdwfoxevaugrnd.png\" ],\n    \"Lesson11HomeMyLibrarySection3\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613921900/StreamCast/Lesson%2011/hg88doxyaojnywkdvijw.png\"],\n    \"Lesson11UpdateHomeScreen1\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613922335/StreamCast/Lesson%2011/lbf5szthhvczt7gzkw19.png\" ],\n    \"Lesson11UpdateContentView1\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613922426/StreamCast/Lesson%2011/ukgj9tzdgvj4axw60qu8.png\" ]\n}"},"slug":"lesson-11-home-screen-part-3","title":"Lesson #11: Home Screen Part 3","updatedDate":"2021-02-22T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZmt5uXZKriBdp4Y85FHh9JGbzVkwL4y4Ey7","type":"zip","id":"a05f4153-256b-5a15-b6b8-efa932a33186","name":"Source code"},{"url":"https://swiftwithmajid.com/2020/07/08/mastering-grids-in-swiftui/","type":"link","id":"ab0f6100-c14e-55a1-afe7-fdbf7fe87ea3","name":"Mastering grid in swiftUI"},{"url":"https://developer.apple.com/documentation/swiftui/lazyvgrid","type":"link","id":"d0fd3d55-ecf1-5f6d-8ea9-3e59d01ffe2b","name":"Apple docs - LazyVGrid"}],"allChapters":null,"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson11RedactedListView0\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613920751/StreamCast/Lesson%2011/fyvnzrrdjkqysoxfk0ij.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613920751/StreamCast/Lesson%2011/fyvnzrrdjkqysoxfk0ij.png\"\n  }))))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson11RedactedListView1\\n// $$liquidcoderFilename-RedactedListView\\n\\nimport SwiftUI\\n\\nstruct RedactedListView: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct RedactedListView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        RedactedListView()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson11RedactedListView2\\n// $$liquidcoderFilename-RedactedListView\\n\\nimport SwiftUI\\n\\nstruct RedactedListView: View {\\n\\n    // highlight-start\\n    enum ViewType {\\n        case podcast, genre\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    var viewType: ViewType\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct RedactedListView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        RedactedListView(viewType: RedactedListView.ViewType.podcast)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson11RedactedListView3\\n// $$liquidcoderFilename-RedactedListView\\n\\nimport SwiftUI\\n\\nstruct RedactedListView: View {\\n\\n    enum ViewType {\\n        case podcast, genre\\n    }\\n\\n    var viewType: ViewType\\n\\n    var body: some View {\\n        // highlight-start\\n        switch viewType {\\n        case .podcast:\\n            ForEach(PodcastViewModel.listPlaceholder) { podcast in\\n                PodcastView(podcast: podcast)\\n            }.redacted(reason: .placeholder)\\n            .padding(.horizontal)\\n\\n        case .genre:\\n            ForEach(GenreViewModel.listPlaceholder) { genre in\\n                GenreView(genre: genre)\\n                    .frame(height: 70)\\n                    .background(Color.overlay)\\n                    .cornerRadius(15)\\n            }.redacted(reason: .placeholder)\\n            .padding(.horizontal)\\n        }\\n\\n        // highlight-end\\n    }\\n}\\n\\nstruct RedactedListView_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        // highlight-start\\n        Group {\\n            VStack {\\n                RedactedListView(viewType: RedactedListView.ViewType.podcast)\\n            }\\n            .previewLayout(.fixed(width: 350.0, height: 400.0))\\n            .preferredColorScheme(.dark)\\n            VStack {\\n                RedactedListView(viewType: RedactedListView.ViewType.genre)\\n\\n            }.previewLayout(.fixed(width: 350.0, height: 400.0))\\n\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson11PodcastLinkView1\\n// $$liquidcoderFilename-PodcastLinkView\\n\\nimport SwiftUI\\n\\nstruct PodcastLinkView: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct PodcastLinkView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        PodcastLinkView()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson11PodcastLinkView2\\n// $$liquidcoderFilename-PodcastLinkView\\n\\nimport SwiftUI\\n\\nstruct PodcastLinkView: View {\\n\\n    // highlight-start\\n    var podcast: PodcastViewModel\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        VStack {\\n            NavigationLink(destination: Text(\\\"To be implemented!\\\") ) {\\n                PodcastView(podcast: podcast)\\n            }\\n\\n            Divider()\\n        }\\n        // highlight-end\\n    }\\n}\\n\\nstruct PodcastLinkView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            PodcastLinkView(podcast: PodcastViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PodcastLinkView(podcast: PodcastViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"div\", {\n    id: \"Lesson11PodcastListContent0\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613921476/StreamCast/Lesson%2011/aynl3markrrps2wqlrmg.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613921476/StreamCast/Lesson%2011/aynl3markrrps2wqlrmg.png\"\n  }))))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson11PodcastListContent1\\n// $$liquidcoderFilename-PodcastListContent\\n\\nimport SwiftUI\\n\\nstruct PodcastListContent: View {\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct PodcastListContent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        PodcastListContent()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson11PodcastListContent2\\n// $$liquidcoderFilename-PodcastListContent\\n\\nimport SwiftUI\\n\\nstruct PodcastListContent: View {\\n\\n    // highlight-start\\n    var genre: String\\n    var limit: Int = 10\\n    // highlight-end\\n\\n    // highlight-start\\n    @EnvironmentObject private var store: Store\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct PodcastListContent_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n    // highlight-end\\n\\n    static var previews: some View {\\n\\n        // highlight-start\\n        Group {\\n            PodcastListContent(genre: \\\"technology\\\")\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.fixed(width: 350.0, height: 400.0))\\n            PodcastListContent(genre: \\\"technology\\\")\\n                .previewLayout(.fixed(width: 350.0, height: 400.0))\\n\\n        }.environmentObject(store)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson11PodcastListContent3\\n// $$liquidcoderFilename-PodcastListContent\\n\\nimport SwiftUI\\n\\nstruct PodcastListContent: View {\\n\\n    var genre: String\\n    var limit: Int = 10\\n\\n    @EnvironmentObject private var store: Store\\n\\n    var body: some View {\\n        // highlight-start\\n        switch getState(for: genre) {\\n        case .loading:\\n            Text(\\\"Loading...\\\")\\n        case .success(let podcasts):\\n            Text(\\\"Successfully fetched \\\\(podcasts.count) podcasts\\\")\\n        case .failure:\\n            Text(\\\"Failed...\\\")\\n        }\\n        // highlight-end\\n    }\\n\\n    // highlight-start\\n    private func getState(for term: String) -> AppState.Result<[PodcastViewModel]> {\\n        if let state = store.apiState.preferredPodcasts[term] {\\n            return state\\n        }\\n        return .loading\\n    }\\n    // highlight-end\\n\\n}\\n\\nstruct PodcastListContent_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n\\n        Group {\\n            VStack {\\n                PodcastListContent(genre: \\\"technology\\\")\\n                    .preferredColorScheme(.dark)\\n                    .previewLayout(.fixed(width: 350.0, height: 400.0))\\n            }\\n\\n            VStack {\\n                PodcastListContent(genre: \\\"technology\\\")\\n                    .previewLayout(.fixed(width: 350.0, height: 400.0))\\n            }\\n\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson11PodcastListContent4\\n// $$liquidcoderFilename-PodcastListContent\\n\\nimport SwiftUI\\n\\nstruct PodcastListContent: View {\\n\\n    var genre: String\\n    var limit: Int = 10\\n\\n    @EnvironmentObject private var store: Store\\n\\n    var body: some View {\\n        switch getState(for: genre) {\\n        case .loading:\\n            // highlight-start\\n            RedactedListView(viewType: .podcast)\\n                .onAppear(perform: fetchPodcasts)\\n            // highlight-end\\n        case .success(let podcasts):\\n            Text(\\\"Succefully fetched \\\\(podcasts.count) podcasts\\\")\\n        case .failure:\\n            Text(\\\"Failed...\\\")\\n        }\\n    }\\n\\n    // highlight-start\\n    private func fetchPodcasts() {\\n        store.dispatch(.api(.fetchPodcasts(genre, limit: limit)))\\n    }\\n    // highlight-end\\n\\n    private func getState(for term: String) -> AppState.Result<[PodcastViewModel]> {\\n        if let state = store.apiState.preferredPodcasts[term] {\\n            return state\\n        }\\n        return .loading\\n    }\\n\\n}\\n\\nstruct PodcastListContent_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n\\n        Group {\\n            VStack {\\n                PodcastListContent(genre: \\\"technology\\\")\\n                    .preferredColorScheme(.dark)\\n                    .previewLayout(.fixed(width: 350.0, height: 400.0))\\n            }\\n\\n            VStack {\\n                PodcastListContent(genre: \\\"technology\\\")\\n                    .previewLayout(.fixed(width: 350.0, height: 400.0))\\n            }\\n\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson11PodcastListContent5\\n// $$liquidcoderFilename-PodcastListContent\\n\\nimport SwiftUI\\n\\nstruct PodcastListContent: View {\\n\\n    var genre: String\\n    var limit: Int = 10\\n\\n    @EnvironmentObject private var store: Store\\n\\n    var body: some View {\\n        switch getState(for: genre) {\\n        case .loading:\\n            RedactedListView(viewType: .podcast)\\n                .onAppear(perform: fetchPodcasts)\\n        case .success(let podcasts):\\n            // highlight-start\\n            successContent(podcasts: podcasts)\\n            // highlight-end\\n        case .failure:\\n            Text(\\\"Failed...\\\")\\n        }\\n    }\\n\\n    // highlight-start\\n    private func successContent(podcasts: [PodcastViewModel]) -> some View {\\n        ForEach(podcasts) { podcast in\\n            PodcastLinkView(podcast: podcast)\\n        }.padding(.horizontal)\\n    }\\n    // highlight-end\\n\\n    private func fetchPodcasts() {\\n        store.dispatch(.api(.fetchPodcasts(genre, limit: limit)))\\n    }\\n\\n    private func getState(for term: String) -> AppState.Result<[PodcastViewModel]> {\\n        if let state = store.apiState.preferredPodcasts[term] {\\n            return state\\n        }\\n        return .loading\\n    }\\n\\n}\\n\\nstruct PodcastListContent_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n\\n        Group {\\n            VStack {\\n                PodcastListContent(genre: \\\"technology\\\")\\n                    .preferredColorScheme(.dark)\\n                    .previewLayout(.fixed(width: 350.0, height: 400.0))\\n            }\\n\\n            VStack {\\n                PodcastListContent(genre: \\\"technology\\\")\\n                    .previewLayout(.fixed(width: 350.0, height: 400.0))\\n            }\\n\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson11PodcastListContent6\\n// $$liquidcoderFilename-PodcastListContent\\n\\nimport SwiftUI\\n\\nstruct PodcastListContent: View {\\n\\n    var genre: String\\n    var limit: Int = 10\\n\\n    @EnvironmentObject private var store: Store\\n\\n    var body: some View {\\n        switch getState(for: genre) {\\n        case .loading:\\n            RedactedListView(viewType: .podcast)\\n                .onAppear(perform: fetchPodcasts)\\n        case .success(let podcasts):\\n            successContent(podcasts: podcasts)\\n        case .failure:\\n        // highlight-start\\n            FailureView(message: \\\"Unable to fetch podcasts for \\\\(Text(genre).bold())\\\", retryAction: fetchPodcasts)\\n            // highlight-end\\n        }\\n    }\\n\\n    private func successContent(podcasts: [PodcastViewModel]) -> some View {\\n        ForEach(podcasts) { podcast in\\n            PodcastLinkView(podcast: podcast)\\n        }.padding(.horizontal)\\n    }\\n\\n    private func fetchPodcasts() {\\n        store.dispatch(.api(.fetchPodcasts(genre, limit: limit)))\\n    }\\n\\n    private func getState(for term: String) -> AppState.Result<[PodcastViewModel]> {\\n        if let state = store.apiState.preferredPodcasts[term] {\\n            return state\\n        }\\n        return .loading\\n    }\\n\\n}\\n\\nstruct PodcastListContent_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n\\n        Group {\\n            VStack {\\n                PodcastListContent(genre: \\\"technology\\\")\\n                    .preferredColorScheme(.dark)\\n                    .previewLayout(.fixed(width: 350.0, height: 400.0))\\n            }\\n\\n            VStack {\\n                PodcastListContent(genre: \\\"technology\\\")\\n                    .previewLayout(.fixed(width: 350.0, height: 400.0))\\n            }\\n\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"div\", {\n    id: \"Lesson11HomeMyLibrarySection0\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613990541/StreamCast/Lesson%2011/fateyiirlmaghryhepet.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613990541/StreamCast/Lesson%2011/fateyiirlmaghryhepet.png\"\n  }))))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson11HomeMyLibrarySection1\\n// $$liquidcoderFilename-HomeMyLibrarySection\\n\\nimport SwiftUI\\n\\nstruct HomeMyLibrarySection: View {\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct HomeMyLibrarySection_Previews: PreviewProvider {\\n\\n    static var previews: some View {\\n        HomeMyLibrarySection()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson11HomeMyLibrarySection2\\n// $$liquidcoderFilename-HomeMyLibrarySection\\n\\nimport SwiftUI\\n\\nstruct HomeMyLibrarySection: View {\\n\\n    // highlight-start\\n    @State private var selectedGenre = \\\"\\\"\\n    // highlight-end\\n\\n    // highlight-start\\n    private var section: some View {\\n        Section(header: HomeMyLibrarySectionHeader(selected: $selectedGenre)) {\\n            PodcastListContent(genre: selectedGenre)\\n        }\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct HomeMyLibrarySection_Previews: PreviewProvider {\\n\\n    static var previews: some View {\\n        HomeMyLibrarySection()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson11HomeMyLibrarySection3\\n// $$liquidcoderFilename-HomeMyLibrarySection\\n\\nimport SwiftUI\\n\\nstruct HomeMyLibrarySection: View {\\n\\n    @State private var selectedGenre = \\\"\\\"\\n\\n    private var section: some View {\\n        Section(header: HomeMyLibrarySectionHeader(selected: $selectedGenre)) {\\n            PodcastListContent(genre: selectedGenre)\\n        }\\n    }\\n\\n    var body: some View {\\n        // highlight-start\\n        LazyVGrid(columns: Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1),\\n                  alignment: .leading,\\n                  spacing: 10,\\n                  pinnedViews: [.sectionHeaders],\\n                  content: { section })\\n        // highlight-end\\n    }\\n}\\n\\nstruct HomeMyLibrarySection_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n    // highlight-end\\n\\n    static var previews: some View {\\n\\n        // highlight-start\\n        Group {\\n            HomeMyLibrarySection()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            HomeMyLibrarySection()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson11UpdateHomeScreen1\\n// $$liquidcoderFilename-Lesson11UpdateHomeScreen1\\n\\nimport SwiftUI\\n\\nstruct HomeScreen: View {\\n    var body: some View {\\n        ScrollView {\\n            // highlight-start\\n            VStack(spacing: 20) {\\n            // highlight-end\\n                HomeMenuList()\\n                HomeGenresContent()\\n                // highlight-start\\n                HomeMyLibrarySection()\\n                // highlight-end\\n            }\\n        }\\n    }\\n}\\n\\nstruct HomeScreen_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            HomeScreen()\\n                .colorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            HomeScreen()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson11UpdateStreamCastApp1\\n// $$liquidcoderFilename-StreamCastApp\\n\\nimport SwiftUI\\n\\n@main\\nstruct StreamCastApp: App {\\n    let persistenceController = PersistenceController.shared\\n    // highlight-start\\n    @ObservedObject private var store = Store()\\n    // highlight-end\\n\\n    var body: some Scene {\\n        WindowGroup {\\n            ContentView()\\n                // highlight-start\\n                .environmentObject(store)\\n                // highlight-end\\n                .environment(\\\\.managedObjectContext, persistenceController.container.viewContext)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson11UpdateContentView1\\n// $$liquidcoderFilename-ContentView\\n\\nimport SwiftUI\\nimport CoreData\\n\\nstruct ContentView: View {\\n\\n    // highlight-start\\n    @FetchRequest(\\n        sortDescriptors:\\n            [NSSortDescriptor(keyPath: \\\\GenreManagedObject.id, ascending: true)]\\n    ) private var genres: FetchedResults<GenreManagedObject>\\n    // highlight-end\\n\\n    var body: some View {\\n\\n    }\\n}\\n\\nstruct ContentView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        ContentView()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson11UpdateContentView2\\n// $$liquidcoderFilename-ContentView\\n\\nimport SwiftUI\\nimport CoreData\\n\\nstruct ContentView: View {\\n\\n    @FetchRequest(\\n        sortDescriptors:\\n            [NSSortDescriptor(keyPath: \\\\GenreManagedObject.id, ascending: true)]\\n    ) private var genres: FetchedResults<GenreManagedObject>\\n\\n    var body: some View {\\n        // highlight-start\\n        if genres.isEmpty {\\n            GenreSelectionScreen()\\n        } else {\\n            NavigationView {\\n                HomeScreen().navigationTitle(\\\"Home\\\")\\n            }\\n        }\\n        // highlight-end\\n    }\\n}\\n\\nstruct ContentView_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n    // highlight-end\\n\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            ContentView()\\n                .preferredColorScheme(.dark)\\n            ContentView()\\n        }.environmentObject(store)\\n\\n        // highlight-end\\n    }\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson11Introduction-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey guys, welcome to lesson number 11 where we will create the remaining views before completing the home screen. In the previous , we created a bunch of views that we will use to complete the home screen. \")), mdx(\"div\", {\n    id: \"Lesson11RedactedListView0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Redacted List View\"), mdx(\"p\", null, \"This view will display a list of placeholder podcasts while the real ones are being fetched. \")), mdx(\"div\", {\n    id: \"Lesson11RedactedListView1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI \\u2192 Components,\")), \" create a new swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"RedactedListView.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only\"))), mdx(\"div\", {\n    id: \"Lesson11RedactedListView2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We first declare a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ViewType\"), \" enum that contains only 2 cases as it will only be used for podcasts and genres.\"), mdx(\"p\", null, \"Then we declare its instance to be used inside the struct.\")), mdx(\"div\", {\n    id: \"Lesson11RedactedListView3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \", we switch on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"viewType\"), \", and in each case we return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ForEach\"), \" view using the respective \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"listPlaceholders\"), \". \"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Note: The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"listPlaceholders\"), \" property in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreViewModel\"), \" struct should be renamed to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"listPlaceholder\"), \". Just \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"right-click\"), \" on it, and select \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Refactor \\u2192 Rename.\")))), mdx(\"div\", {\n    id: \"Lesson11PodcastLinkView1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"PodcastLink View\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI \\u2192 Elements,\")), \" create a new swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PodcastLinkView.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only\"))), mdx(\"div\", {\n    id: \"Lesson11PodcastLinkView2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We first declare a podcast property. Then inside \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \", we surround the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastView\"), \" with a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NavigationLink\"), \".\"), mdx(\"p\", null, \"The destination will be implemented later on, but for now, we are just putting a simple \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" view as placeholder.\")), mdx(\"div\", {\n    id: \"Lesson11PodcastListContent0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"PodcastList Content\"), mdx(\"p\", null, \"The podcast list content will display fetched podcasts in a scrollable collection. This is the list that will appear after the redacted one.\")), mdx(\"div\", {\n    id: \"Lesson11PodcastListContent1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI \\u2192 Components,\")), \" create a new swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PodcastListContent.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only\"))), mdx(\"div\", {\n    id: \"Lesson11PodcastListContent2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We declare a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"genre\"), \" and the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"limit\"), \" properties that we will use as the term and limit respectively when fetching podcasts\"), mdx(\"p\", null, \"We bring in the store to fetch podcasts with.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"We create the preview store in the preview provider that we apply to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Group\"), \".\"))), mdx(\"div\", {\n    id: \"Lesson11PodcastListContent3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"getState\"), \" checks whether we have podcasts in the dictionary for the given term, and return that state, otherwise we return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"loading\"), \" one.\"), mdx(\"p\", null, \"using that function, we switch on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" returned by it to create views for each case. right now we are just displaying some placeholder \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" views.\")), mdx(\"div\", {\n    id: \"Lesson11PodcastListContent4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"When loading podcasts, we return the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RedactedListView\"), \" for podcast, and on it we attach a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onAppear\"), \" modifier passing in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fetchPodcasts\"), \" that does exactly what it says, dispatches an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"api\"), \" action to fetch podcasts.\")), mdx(\"div\", {\n    id: \"Lesson11PodcastListContent5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"When we get back a successful result, we create and return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ForEach\"), \" view enumerating over the podcasts array, and creating a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastLinkView\"), \" for each podcast object.\")), mdx(\"div\", {\n    id: \"Lesson11PodcastListContent6-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Last, but not least, we  return the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"FailureView\"), \" with appropriate message when we get a failure result. We are also passing the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fetchPodcasts\"), \" in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"retryAction\"), \" parameter to allow the user to retry fetching podcasts. \")), mdx(\"div\", {\n    id: \"Lesson11HomeMyLibrarySection0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"HomeMyLibrarySection\"), mdx(\"p\", null, \"As you can see on the right, this will create the entire bottom section of the home screen.  \")), mdx(\"div\", {\n    id: \"Lesson11HomeMyLibrarySection1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens \\u2192\")), \" \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Home \\u2192 Components,\")), \" create a new swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"HomeMyLibrarySection.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only\"))), mdx(\"div\", {\n    id: \"Lesson11HomeMyLibrarySection2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The only property we need here is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"selectedGenre\"), \" that will hold on the selected genre name.\"), mdx(\"p\", null, \"Next, we create the section that returns a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Section\"), \" view passing in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeMyLibrarySectionHeader\"), \" , and binding the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"selected\"), \" to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"selectedGenre\"), \" state in order to get notified when it changes.\"), mdx(\"p\", null, \"Inside the section content block, we return the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastListContent\"), \" created in the previous chapter.\")), mdx(\"div\", {\n    id: \"Lesson11HomeMyLibrarySection3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the body, we return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LazyVGrid\"), \", we want the section header to be pinned when the we scroll to top that's why we set the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"pinnedViews\"), \" to  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"[sectionHeaders]\"), \".\"), mdx(\"p\", null, \"Learn more about grid \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://swiftwithmajid.com/2020/07/08/mastering-grids-in-swiftui/\"\n  }), \"here\"), \"  and \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://developer.apple.com/documentation/swiftui/lazyvgrid\"\n  }), \"here\")), mdx(\"hr\", null), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"We need the store in the preview even though  we don't declare it in the view itself. This is because the PreviewProvider is a separate environment, so it needs a root store to work.\"))), mdx(\"div\", {\n    id: \"Lesson11UpdateHomeScreen1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the home screen\"), mdx(\"p\", null, \"First of all, replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LazyVStack\"), \" with a simple \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \", we don't need to be lazy here because we only have 3 items to stack, then  add the highlighted line of code in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" view, then resume the preview.\")), mdx(\"div\", {\n    id: \"Lesson11UpdateStreamCastApp1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the StreamCastApp\"), mdx(\"p\", null, \"We declare the store property as an observed object that we then attach to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ContentView\"), \" using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"environmentObject\"), \" modifier. \"), mdx(\"p\", null, \"Doing this will allow us to declare the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"store\"), \" object in any view using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"@EnvironmentObject\"), \" property wrapper.\")), mdx(\"div\", {\n    id: \"Lesson11UpdateContentView1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"ContentView\"), mdx(\"p\", null, \"First remove everything inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ContentView\"), \" struct.\"), mdx(\"p\", null, \"We use the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"@FetchRequest\"), \" property wrapper to declare the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"genres\"), \" in order to be notified whenever  we save new \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"genres\"), \" in the database. This will also fetch saved genres on first load.\")), mdx(\"div\", {\n    id: \"Lesson11UpdateContentView2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We check if \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"genres\"), \" is empty, then show the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreSelectionScreen\"), \" to the user for her or him to choose from and saved her or his preferred genres before showing the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeScreen\"), \". In case there are already genres in the database, we skip the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreSelectionScreen\"), \" and show the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeScreen\"), \".\"), mdx(\"hr\", null), mdx(\"p\", null, \"Now you can run the app in the simulator, and you should be able to see the genres selection screen if it's the first time or the home screen if it's not.\")), mdx(\"div\", {\n    id: \"Lesson11Outro-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And this brings lesson 11 to a close. Let's move on to lesson 12 where we will create screens for all the links we have so far. Happy coding guys!\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#redacted-list-view","title":"Redacted List View"},{"url":"#podcastlink-view","title":"PodcastLink View"},{"url":"#podcastlist-content","title":"PodcastList Content"},{"url":"#homemylibrarysection","title":"HomeMyLibrarySection"},{"url":"#updating-the-home-screen","title":"Updating the home screen"},{"url":"#updating-the-streamcastapp","title":"Updating the StreamCastApp"},{"url":"#contentview","title":"ContentView"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Hey guys, welcome to lesson number 11 where we will create the remaining views before completing the home screen. In the previous , we created a bunch of views that we will use to complete the home screen. \"), mdx(\"h2\", null, \"Redacted List View\"), mdx(\"p\", null, \"This view will display a list of placeholder podcasts while the real ones are being fetched. \"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI \\u2192 Components,\")), \" create a new swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"RedactedListView.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-RedactedListView\\n\\nimport SwiftUI\\n\\nstruct RedactedListView: View {\\n\\n    // highlight-start\\n    enum ViewType {\\n        case podcast, genre\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    var viewType: ViewType\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct RedactedListView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        RedactedListView(viewType: RedactedListView.ViewType.podcast)\\n    }\\n}\\n\")), mdx(\"p\", null, \"We first declare a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ViewType\"), \" enum that contains only 2 cases as it will only be used for podcasts and genres.\"), mdx(\"p\", null, \"Then we declare its instance to be used inside the struct.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-RedactedListView\\n\\nimport SwiftUI\\n\\nstruct RedactedListView: View {\\n\\n    enum ViewType {\\n        case podcast, genre\\n    }\\n\\n    var viewType: ViewType\\n\\n    var body: some View {\\n        // highlight-start\\n        switch viewType {\\n        case .podcast:\\n            ForEach(PodcastViewModel.listPlaceholder) { podcast in\\n                PodcastView(podcast: podcast)\\n            }.redacted(reason: .placeholder)\\n            .padding(.horizontal)\\n\\n        case .genre:\\n            ForEach(GenreViewModel.listPlaceholder) { genre in\\n                GenreView(genre: genre)\\n                    .frame(height: 70)\\n                    .background(Color.overlay)\\n                    .cornerRadius(15)\\n            }.redacted(reason: .placeholder)\\n            .padding(.horizontal)\\n        }\\n\\n        // highlight-end\\n    }\\n}\\n\\nstruct RedactedListView_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        // highlight-start\\n        Group {\\n            VStack {\\n                RedactedListView(viewType: RedactedListView.ViewType.podcast)\\n            }\\n            .previewLayout(.fixed(width: 350.0, height: 400.0))\\n            .preferredColorScheme(.dark)\\n            VStack {\\n                RedactedListView(viewType: RedactedListView.ViewType.genre)\\n\\n            }.previewLayout(.fixed(width: 350.0, height: 400.0))\\n\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \", we switch on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"viewType\"), \", and in each case we return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ForEach\"), \" view using the respective \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"listPlaceholders\"), \". \"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Note: The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"listPlaceholders\"), \" property in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreViewModel\"), \" struct should be renamed to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"listPlaceholder\"), \". Just \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"right-click\"), \" on it, and select \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Refactor \\u2192 Rename.\"))), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613920751/StreamCast/Lesson%2011/fyvnzrrdjkqysoxfk0ij.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613920751/StreamCast/Lesson%2011/fyvnzrrdjkqysoxfk0ij.png\"\n  }))), mdx(\"h2\", null, \"PodcastLink View\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI \\u2192 Elements,\")), \" create a new swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PodcastLinkView.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-PodcastLinkView\\n\\nimport SwiftUI\\n\\nstruct PodcastLinkView: View {\\n\\n    // highlight-start\\n    var podcast: PodcastViewModel\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        VStack {\\n            NavigationLink(destination: Text(\\\"To be implemented!\\\") ) {\\n                PodcastView(podcast: podcast)\\n            }\\n\\n            Divider()\\n        }\\n        // highlight-end\\n    }\\n}\\n\\nstruct PodcastLinkView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            PodcastLinkView(podcast: PodcastViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PodcastLinkView(podcast: PodcastViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"We first declare a podcast property. Then inside \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \", we surround the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastView\"), \" with a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NavigationLink\"), \".\"), mdx(\"p\", null, \"The destination will be implemented later on, but for now, we are just putting a simple \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" view as placeholder.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613920901/StreamCast/Lesson%2011/yt9nfiaszpm6xxzqican.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613920901/StreamCast/Lesson%2011/yt9nfiaszpm6xxzqican.png\"\n  }))), mdx(\"h2\", null, \"PodcastList Content\"), mdx(\"p\", null, \"The podcast list content will display fetched podcasts in a scrollable collection. This is the list that will appear after the redacted one.\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI \\u2192 Components,\")), \" create a new swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PodcastListContent.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-PodcastListContent\\n\\nimport SwiftUI\\n\\nstruct PodcastListContent: View {\\n\\n    // highlight-start\\n    var genre: String\\n        var limit: Int = 10\\n    // highlight-end\\n\\n    // highlight-start\\n    @EnvironmentObject private var store: Store\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct PodcastListContent_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n    // highlight-end\\n\\n    static var previews: some View {\\n\\n        // highlight-start\\n        Group {\\n            PodcastListContent(genre: \\\"technology\\\")\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.fixed(width: 350.0, height: 400.0))\\n            PodcastListContent(genre: \\\"technology\\\")\\n                .previewLayout(.fixed(width: 350.0, height: 400.0))\\n\\n        }.environmentObject(store)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"We declare a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"genre\"), \" and the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"limit\"), \" properties that we will use as the term and limit respectively when fetching podcasts\"), mdx(\"p\", null, \"We bring in the store to fetch podcasts with.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"We create the preview store  in the preview provider that we apply to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Group\"), \".\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-PodcastListContent\\n\\nimport SwiftUI\\n\\nstruct PodcastListContent: View {\\n\\n    var genre: String\\n    var limit: Int = 10\\n\\n    @EnvironmentObject private var store: Store\\n\\n    var body: some View {\\n        // highlight-start\\n        switch getState(for: genre) {\\n        case .loading:\\n            Text(\\\"Loading...\\\")\\n        case .success(let podcasts):\\n            Text(\\\"Successfully fetched \\\\(podcasts.count) podcasts\\\")\\n        case .failure:\\n            Text(\\\"Failed...\\\")\\n\\n        }\\n        // highlight-end\\n    }\\n\\n    // highlight-start\\n    private func getState(for term: String) -> AppState.Result<[PodcastViewModel]> {\\n        if let state = store.apiState.preferredPodcasts[term] {\\n            return state\\n        }\\n        return .loading\\n    }\\n    // highlight-end\\n\\n}\\n\\nstruct PodcastListContent_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n\\n        Group {\\n            VStack {\\n                PodcastListContent(genre: \\\"technology\\\")\\n                    .preferredColorScheme(.dark)\\n                    .previewLayout(.fixed(width: 350.0, height: 400.0))\\n            }\\n\\n            VStack {\\n                PodcastListContent(genre: \\\"technology\\\")\\n                    .previewLayout(.fixed(width: 350.0, height: 400.0))\\n            }\\n\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"getState\"), \" checks whether we have podcasts in the dictionary for the given term, and return that state, otherwise we return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"loading\"), \" one.\"), mdx(\"p\", null, \"using that function, we switch on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" returned by it to create views for each case. right now we are just displaying some placeholder \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" views.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\n// $$liquidcoderFilename-PodcastListContent\\n\\nimport SwiftUI\\n\\nstruct PodcastListContent: View {\\n\\n    var genre: String\\n    var limit: Int = 10\\n\\n    @EnvironmentObject private var store: Store\\n\\n    var body: some View {\\n        switch getState(for: genre) {\\n        case .loading:\\n            // highlight-start\\n            RedactedListView(viewType: .podcast)\\n                .onAppear(perform: fetchPodcasts)\\n            // highlight-end\\n        case .success(let podcasts):\\n            Text(\\\"Succefully fetched \\\\(podcasts.count) podcasts\\\")\\n        case .failure:\\n            Text(\\\"Failed...\\\")\\n\\n        }\\n    }\\n\\n    // highlight-start\\n    private func fetchPodcasts() {\\n        store.dispatch(.api(.fetchPodcasts(genre, limit: limit)))\\n    }\\n    // highlight-end\\n\\n    private func getState(for term: String) -> AppState.Result<[PodcastViewModel]> {\\n        if let state = store.apiState.preferredPodcasts[term] {\\n            return state\\n        }\\n        return .loading\\n    }\\n\\n}\\n\\nstruct PodcastListContent_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n\\n        Group {\\n            VStack {\\n                PodcastListContent(genre: \\\"technology\\\")\\n                    .preferredColorScheme(.dark)\\n                    .previewLayout(.fixed(width: 350.0, height: 400.0))\\n            }\\n\\n            VStack {\\n                PodcastListContent(genre: \\\"technology\\\")\\n                    .previewLayout(.fixed(width: 350.0, height: 400.0))\\n            }\\n\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"p\", null, \"When loading podcasts, we return the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RedactedListView\"), \" for podcast, and on it we attach a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onAppear\"), \" modifier passing in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fetchPodcasts\"), \" that does exactly what it says, dispatches an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"api\"), \" action to fetch podcasts.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613921104/StreamCast/Lesson%2011/hh3yruhimqaaikkvew11.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613921104/StreamCast/Lesson%2011/hh3yruhimqaaikkvew11.png\"\n  }))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-PodcastListContent\\n\\nimport SwiftUI\\n\\nstruct PodcastListContent: View {\\n\\n    var genre: String\\n    var limit: Int = 10\\n\\n    @EnvironmentObject private var store: Store\\n\\n    var body: some View {\\n        switch getState(for: genre) {\\n        case .loading:\\n            RedactedListView(viewType: .podcast)\\n                .onAppear(perform: fetchPodcasts)\\n        case .success(let podcasts):\\n            // highlight-start\\n            successContent(podcasts: podcasts)\\n            // highlight-end\\n        case .failure:\\n            Text(\\\"Failed...\\\")\\n\\n        }\\n    }\\n\\n    // highlight-start\\n    private func successContent(podcasts: [PodcastViewModel]) -> some View {\\n        ForEach(podcasts) { podcast in\\n            PodcastLinkView(podcast: podcast)\\n        }.padding(.horizontal)\\n    }\\n    // highlight-end\\n\\n    private func fetchPodcasts() {\\n        store.dispatch(.api(.fetchPodcasts(genre, limit: limit)))\\n    }\\n\\n    private func getState(for term: String) -> AppState.Result<[PodcastViewModel]> {\\n        if let state = store.apiState.preferredPodcasts[term] {\\n            return state\\n        }\\n        return .loading\\n    }\\n\\n}\\n\\nstruct PodcastListContent_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n\\n        Group {\\n            VStack {\\n                PodcastListContent(genre: \\\"technology\\\")\\n                    .preferredColorScheme(.dark)\\n                    .previewLayout(.fixed(width: 350.0, height: 400.0))\\n            }\\n\\n            VStack {\\n                PodcastListContent(genre: \\\"technology\\\")\\n                    .previewLayout(.fixed(width: 350.0, height: 400.0))\\n            }\\n\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"p\", null, \"When we get back a successful result, we create and return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ForEach\"), \" view enumerating over the podcasts array, and creating a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastLinkView\"), \" for each podcast object.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-PodcastListContent\\n\\nimport SwiftUI\\n\\nstruct PodcastListContent: View {\\n\\n    var genre: String\\n    var limit: Int = 10\\n\\n    @EnvironmentObject private var store: Store\\n\\n    var body: some View {\\n        switch getState(for: genre) {\\n        case .loading:\\n            RedactedListView(viewType: .podcast)\\n                .onAppear(perform: fetchPodcasts)\\n        case .success(let podcasts):\\n            successContent(podcasts: podcasts)\\n        case .failure:\\n        // highlight-start\\n            FailureView(message: \\\"Unable to fetch podcasts for \\\\(Text(genre).bold())\\\", retryAction: fetchPodcasts)\\n            // highlight-end\\n        }\\n    }\\n\\n    // highlight-start\\n    private func successContent(podcasts: [PodcastViewModel]) -> some View {\\n        ForEach(podcasts) { podcast in\\n            PodcastLinkView(podcast: podcast)\\n        }.padding(.horizontal)\\n    }\\n    // highlight-end\\n\\n    private func fetchPodcasts() {\\n        store.dispatch(.api(.fetchPodcasts(genre, limit: limit)))\\n    }\\n\\n    private func getState(for term: String) -> AppState.Result<[PodcastViewModel]> {\\n        if let state = store.apiState.preferredPodcasts[term] {\\n            return state\\n        }\\n        return .loading\\n    }\\n\\n}\\n\\nstruct PodcastListContent_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n\\n        Group {\\n            VStack {\\n                PodcastListContent(genre: \\\"technology\\\")\\n                    .preferredColorScheme(.dark)\\n                    .previewLayout(.fixed(width: 350.0, height: 400.0))\\n            }\\n\\n            VStack {\\n                PodcastListContent(genre: \\\"technology\\\")\\n                    .previewLayout(.fixed(width: 350.0, height: 400.0))\\n            }\\n\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"p\", null, \"Last, but not least, we  return the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"FailureView\"), \" with appropriate message when we get a failure result. We are also passing the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fetchPodcasts\"), \" in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"retryAction\"), \" parameter to allow the user to retry fetching podcasts. \"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613921476/StreamCast/Lesson%2011/aynl3markrrps2wqlrmg.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613921476/StreamCast/Lesson%2011/aynl3markrrps2wqlrmg.png\"\n  }))), mdx(\"h2\", null, \"HomeMyLibrarySection\"), mdx(\"p\", null, \"This will create the entire bottom section of the home screen. \"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens \\u2192\")), \" \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Home \\u2192 Components,\")), \" create a new swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PodcastListContent.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson11HomeMyLibrarySection2\\n// $$liquidcoderFilename-HomeMyLibrarySection\\n\\nimport SwiftUI\\n\\nstruct HomeMyLibrarySection: View {\\n\\n    // highlight-start\\n    @State private var selectedGenre = \\\"\\\"\\n    // highlight-end\\n\\n    // highlight-start\\n    private var section: some View {\\n        Section(header: HomeMyLibrarySectionHeader(selected: $selectedGenre)) {\\n            PodcastListContent(genre: selectedGenre)\\n        }\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct HomeMyLibrarySection_Previews: PreviewProvider {\\n\\n    static var previews: some View {\\n        HomeMyLibrarySection()\\n    }\\n}\\n\")), mdx(\"p\", null, \"The only property we need here is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"selectedGenre\"), \" that will hold on the selected genre name.\"), mdx(\"p\", null, \"Next, we create the section that returns a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Section\"), \" view passing in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeMyLibrarySectionHeader\"), \" , and binding the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"selected\"), \" to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"selectedGenre\"), \" state in order to get notified when it changes.\"), mdx(\"p\", null, \"Inside the section content block, we return the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastListContent\"), \" created in the previous chapter.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson11HomeMyLibrarySection3\\n// $$liquidcoderFilename-HomeMyLibrarySection\\n\\nimport SwiftUI\\n\\nstruct HomeMyLibrarySection: View {\\n\\n    @State private var selectedGenre = \\\"\\\"\\n\\n    private var section: some View {\\n        Section(header: HomeMyLibrarySectionHeader(selected: $selectedGenre)) {\\n            PodcastListContent(genre: selectedGenre)\\n        }\\n    }\\n\\n    var body: some View {\\n        // highlight-start\\n        LazyVGrid(columns: Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1),\\n                  alignment: .leading,\\n                  spacing: 10,\\n                  pinnedViews: [.sectionHeaders],\\n                  content: { section })\\n        // highlight-end\\n    }\\n}\\n\\nstruct HomeMyLibrarySection_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n    // highlight-end\\n\\n    static var previews: some View {\\n\\n        // highlight-start\\n        Group {\\n            HomeMyLibrarySection()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            HomeMyLibrarySection()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"In the body, we return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LazyVGrid\"), \", we want the section header to be pinned when the we scroll to top that's why we set the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"pinnedViews\"), \" to  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"[sectionHeaders]\"), \".\"), mdx(\"p\", null, \"Learn more about grid \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://swiftwithmajid.com/2020/07/08/mastering-grids-in-swiftui/\"\n  }), \"here\"), \"  and \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://developer.apple.com/documentation/swiftui/lazyvgrid\"\n  }), \"here\")), mdx(\"hr\", null), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"We need the store in the preview even though  we don't declare it in the view itself. This is because the PreviewProvider is a separate environment, so it needs a root store to work.\")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613921900/StreamCast/Lesson%2011/hg88doxyaojnywkdvijw.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613921900/StreamCast/Lesson%2011/hg88doxyaojnywkdvijw.png\"\n  }))), mdx(\"h2\", null, \"Updating the home screen\"), mdx(\"p\", null, \"First of all, replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LazyVStack\"), \" with a simple \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \", we don't need to be lazy here because we only have 3 items to stack, then  add the highlighted line of code in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" view, then resume the preview.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson11UpdateHomeScreen1\\n// $$liquidcoderFilename-Lesson11UpdateHomeScreen1\\n\\nimport SwiftUI\\n\\nstruct HomeScreen: View {\\n    var body: some View {\\n        ScrollView {\\n            // highlight-start\\n            VStack(spacing: 20) {\\n            // highlight-end\\n                HomeMenuList()\\n                HomeGenresContent()\\n                // highlight-start\\n                HomeMyLibrarySection()\\n                // highlight-end\\n            }\\n        }\\n    }\\n}\\n\\nstruct HomeScreen_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            HomeScreen()\\n                .colorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            HomeScreen()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613922335/StreamCast/Lesson%2011/lbf5szthhvczt7gzkw19.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613922335/StreamCast/Lesson%2011/lbf5szthhvczt7gzkw19.png\"\n  }))), mdx(\"h2\", null, \"Updating the StreamCastApp\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson11UpdateStreamCastApp1\\n// $$liquidcoderFilename-StreamCastApp\\n\\nimport SwiftUI\\n\\n@main\\nstruct StreamCastApp: App {\\n    let persistenceController = PersistenceController.shared\\n    // highlight-start\\n    @ObservedObject private var store = Store()\\n    // highlight-end\\n\\n    var body: some Scene {\\n        WindowGroup {\\n            ContentView()\\n                // highlight-start\\n                .environmentObject(store)\\n                // highlight-end\\n                .environment(\\\\.managedObjectContext, persistenceController.container.viewContext)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"We declare the store property as an observed object that we then attach to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ContentView\"), \" using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"environmentObject\"), \" modifier. \"), mdx(\"p\", null, \"Doing this will allow us to declare the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"store\"), \" object in any view using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"@EnvironmentObject\"), \" property wrapper.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson11UpdateContentView1\\n// $$liquidcoderFilename-ContentView\\n\\nimport SwiftUI\\n\\nstruct ContentView: View {\\n\\n    // highlight-start\\n    @FetchRequest(\\n        sortDescriptors:\\n            [NSSortDescriptor(keyPath: \\\\GenreManagedObject.id, ascending: true)]\\n    ) private var genres: FetchedResults<GenreManagedObject>\\n    // highlight-end\\n\\n    var body: some View {\\n        // highlight-start\\n        if genres.isEmpty {\\n            GenreSelectionScreen()\\n        } else {\\n            NavigationView {\\n                HomeScreen().navigationTitle(\\\"Home\\\")\\n            }\\n        }\\n        // highlight-end\\n    }\\n}\\n\\nstruct ContentView_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n    // highlight-end\\n\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            ContentView()\\n                .preferredColorScheme(.dark)\\n                .environmentObject(store)\\n            ContentView()\\n                .environmentObject(store)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"We use the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"@FetchRequest\"), \" property wrapper to declare the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"genres\"), \"in order to be notified whenever  we save \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"genres\"), \" in the database. This will also fetch saved genres on first load.\"), mdx(\"p\", null, \"We check if \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"genres\"), \" is empty, then show the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreSelectionScreen\"), \" to the user for her or him to choose from and saved her or his preferred genres before showing the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeScreen\"), \". In case there are already genres in the database, we skip the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreSelectionScreen\"), \" and show the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeScreen\"), \".\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613922426/StreamCast/Lesson%2011/ukgj9tzdgvj4axw60qu8.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1613922426/StreamCast/Lesson%2011/ukgj9tzdgvj4axw60qu8.png\"\n  }))), mdx(\"p\", null, \"Now you can run the app on the simulator, and you should be able to see the genres selection screen if it's the first time or the home screen if it's not.\"), mdx(\"p\", null, \"And this brings lesson 11 to a close. Let's move on to lesson 12 where we will create screens for all the links we have created. Happy coding guys!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#redacted-list-view","title":"Redacted List View"},{"url":"#podcastlink-view","title":"PodcastLink View"},{"url":"#podcastlist-content","title":"PodcastList Content"},{"url":"#homemylibrarysection","title":"HomeMyLibrarySection"},{"url":"#updating-the-home-screen","title":"Updating the home screen"},{"url":"#updating-the-streamcastapp","title":"Updating the StreamCastApp"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-02-24T09:42:51.787Z","isNewLayout":true,"previews":{"previews":"{\n    \"Lesson12AllGenresScreen1\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\" ],\n    \"Lesson12AllGenresScreen2\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\" ],\n    \"Lesson12AllGenresScreen5\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1614069967/StreamCast/Lesson%2012/qkeec9psrkjjcgtxovuk.png\" ],\n    \"Lesson12AllGenresScreen6\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1614070744/StreamCast/Lesson%2012/qgeo5bmy1qxec0ez96cq.png\" ],\n    \"Lesson12PodcastListScreen1\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\" ],\n    \"Lesson12PodcastListScreen2\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\" ],\n    \"Lesson12PodcastListScreen3\": [ \"https://res.cloudinary.com/liquidcoder/image/upload/v1613921104/StreamCast/Lesson%2011/hh3yruhimqaaikkvew11.png\" ]\n}"},"slug":"lesson-12-navigation-list-screens","title":"Lesson #12: Navigation List Screens","updatedDate":"2021-02-24T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZnxhuXZmXF3fR503208wYaO9waB24Wi0UMk","type":"zip","id":"96ae0b97-c724-5b48-9138-be88819d95f2","name":"Source code"}],"allChapters":null,"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson12AllGenresScreen0\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614070744/StreamCast/Lesson%2012/qgeo5bmy1qxec0ez96cq.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614070744/StreamCast/Lesson%2012/qgeo5bmy1qxec0ez96cq.png\"\n  }))))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson12AllGenresScreen1\\n// $$liquidcoderFilename-AllGenresScreen\\n\\nimport SwiftUI\\n\\nstruct AllGenresScreen: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct AllGenresScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        AllGenresScreen()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson12AllGenresScreen2\\n// $$liquidcoderFilename-AllGenresScreen\\n\\nimport SwiftUI\\n\\nstruct AllGenresScreen: View {\\n\\n    // highlight-start\\n    @EnvironmentObject private var store: Store\\n\\n    private let genresFilename = \\\"genres\\\"\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct AllGenresScreen_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n    // highlight-end\\n\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            AllGenresScreen()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n                .environmentObject(store)\\n            AllGenresScreen()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson12AllGenresScreen3\\n// $$liquidcoderFilename-AllGenresScreen\\n\\nimport SwiftUI\\n\\nstruct AllGenresScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    private let genresFilename = \\\"genres\\\"\\n\\n    // highlight-start\\n    private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n    // highlight-end\\n\\n    var body: some View {\\n        // highlight-start\\n        ScrollView {\\n            LazyVGrid(columns: gridItems,\\n                      alignment: .leading,\\n                      spacing: 20,\\n                  content: { content })\\n        }.navigationTitle(\\\"All genres\\\")\\n        // highlight-end\\n    }\\n}\\n\\nstruct AllGenresScreen_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            AllGenresScreen()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n                .environmentObject(store)\\n            AllGenresScreen()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson12AllGenresScreen4\\n// $$liquidcoderFilename-AllGenresScreen\\n\\nimport SwiftUI\\n\\nstruct AllGenresScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    private let genresFilename = \\\"genres\\\"\\n\\n    private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n\\n    // highlight-start\\n    private var content: some View {\\n        Group {\\n            switch store.apiState.allGenres {\\n            case .loading:\\n\\n            case .success(let genres):\\n\\n            case .failure:\\n\\n            }\\n        }\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        ScrollView {\\n            LazyVGrid(columns: gridItems,\\n                      alignment: .leading,\\n                      spacing: 20,\\n                  content: { content })\\n        }.navigationTitle(\\\"All genres\\\")\\n    }\\n}\\n\\nstruct AllGenresScreen_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            AllGenresScreen()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n                .environmentObject(store)\\n            AllGenresScreen()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson12AllGenresScreen5\\n// $$liquidcoderFilename-AllGenresScreen\\n\\nimport SwiftUI\\n\\nstruct AllGenresScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    private let genresFilename = \\\"genres\\\"\\n\\n    private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n\\n    private var content: some View {\\n        Group {\\n            switch store.apiState.allGenres {\\n            case .loading:\\n                // highlight-start\\n                RedactedListView(viewType: .genre)\\n                    .onAppear(perform: fetchGenres)\\n            // highlight-end\\n            case .success(let genres):\\n\\n            case .failure:\\n                // highlight-start\\n                FailureView(message: \\\"Unable to load genres\\\", showIcon: true, retryAction: fetchGenres)\\n                // highlight-end\\n            }\\n        }\\n    }\\n\\n    var body: some View {\\n        ScrollView {\\n            LazyVGrid(columns: gridItems,\\n                      alignment: .leading,\\n                      spacing: 20,\\n                  content: { content })\\n        }.navigationTitle(\\\"All genres\\\")\\n    }\\n\\n    // highlight-start\\n    private func fetchGenres() {\\n        store.dispatch(.api(.fetchGenres(genresFilename)))\\n    }\\n    // highlight-end\\n}\\n\\nstruct AllGenresScreen_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            AllGenresScreen()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n                .environmentObject(store)\\n            AllGenresScreen()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson12AllGenresScreen6\\n// $$liquidcoderFilename-AllGenresScreen\\n\\nimport SwiftUI\\n\\nstruct AllGenresScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    private let genresFilename = \\\"genres\\\"\\n\\n    private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n\\n    private var content: some View {\\n        Group {\\n            switch store.apiState.allGenres {\\n            case .loading:\\n                RedactedListView(viewType: .genre)\\n                    .onAppear(perform: fetchGenres)\\n            case .success(let genres):\\n            // highlight-start\\n                createSuccess(genres: genres)\\n            // highlight-end\\n            case .failure:\\n                FailureView(message: \\\"Unable to load genres\\\", showIcon: true, retryAction: fetchGenres)\\n            }\\n        }\\n    }\\n\\n    var body: some View {\\n        ScrollView {\\n            LazyVGrid(columns: gridItems,\\n                      alignment: .leading,\\n                      spacing: 20,\\n                      content: { content })\\n        }.navigationTitle(\\\"All genres\\\")\\n    }\\n\\n    // highlight-start\\n    private func createSuccess(genres: [GenreViewModel]) -> some View {\\n        ForEach(genres) { genre in\\n            GenreLinkView(genre: genre)\\n                .frame(height: 70)\\n        }.padding(.horizontal)\\n    }\\n    // highlight-end\\n\\n    private func fetchGenres() {\\n        store.dispatch(.api(.fetchGenres(genresFilename)))\\n    }\\n}\\n\\nstruct AllGenresScreen_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            AllGenresScreen()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n                .environmentObject(store)\\n            AllGenresScreen()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson12UpdateHomeSectionHeaderView1\\n// $$liquidcoderFilename-HomeSectionHeaderView\\n\\nimport SwiftUI\\n\\nstruct HomeSectionHeaderView: View {\\n\\n    var title: String\\n\\n    var body: some View {\\n        HStack {\\n            Text(title)\\n                .font(.title2)\\n                .bold()\\n            Spacer()\\n\\n            SeeAllLink(destination: {\\n                // highlight-start\\n                AllGenresScreen()\\n                // highlight-end\\n            }).padding(.vertical, 7)\\n            .cornerRadius(10)\\n            .background(Color.clear)\\n\\n        }.padding(.horizontal)\\n    }\\n}\\n\\nstruct HeaderView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            HomeSectionHeaderView(title: \\\"Trending\\\")\\n                .background(Color.background)\\n                .colorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n\\n            HomeSectionHeaderView(title: \\\"Trending\\\")\\n                .colorScheme(.light)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"div\", {\n    id: \"Lesson12PodcastListScreen0\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614071352/StreamCast/Lesson%2012/aenvt06nehypvwj4xepl.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614071352/StreamCast/Lesson%2012/aenvt06nehypvwj4xepl.png\"\n  }))))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson12PodcastListScreen1\\n// $$liquidcoderFilename-PodcastListScreen\\n\\nimport SwiftUI\\n\\nstruct PodcastListScreen: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct PodcastListScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        PodcastListScreen()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson12PodcastListScreen2\\n// $$liquidcoderFilename-PodcastListScreen\\n\\nimport SwiftUI\\n\\nstruct PodcastListScreen: View {\\n\\n    // highlight-start\\n    var genre: String\\n    // highlight-end\\n\\n    // highlight-start\\n    private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct PodcastListScreen_Previews: PreviewProvider {\\n\\n        // highlight-start\\n        static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))    \\n         // highlight-end\\n\\n        static var previews: some View {\\n        // highlight-start\\n        Group {\\n            PodcastListScreen(genre: \\\"technology\\\")\\n                .preferredColorScheme(.dark)\\n            PodcastListScreen(genre: \\\"technology\\\")\\n        }.environmentObject(store)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson12PodcastListScreen3\\n// $$liquidcoderFilename-PodcastListScreen\\n\\nimport SwiftUI\\n\\nstruct PodcastListScreen: View {\\n\\n    var genre: String\\n\\n    private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n\\n    // highlight-start\\n    private var grid: some View {\\n        LazyVGrid(columns: gridItems,\\n                  alignment: .leading,\\n                  spacing: 10,\\n                  content: { PodcastListContent(genre: genre, limit: 100) })\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        ScrollView {\\n            grid\\n        }.navigationTitle(genre)\\n        // highlight-end\\n    }\\n}\\n\\nstruct PodcastListScreen_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            PodcastListScreen(genre: \\\"technology\\\")\\n                .preferredColorScheme(.dark)\\n            PodcastListScreen(genre: \\\"technology\\\")\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson12UpdateGenreLinkView1\\n// $$liquidcoderFilename-GenreLinkView\\n\\nimport SwiftUI\\n\\nstruct GenreLinkView: View {\\n\\n    var genre: GenreViewModel\\n\\n    var body: some View {\\n\\n        NavigationLink(\\n            // highlight-start\\n            destination: PodcastListScreen(genre: genre.name),\\n            // highlight-end\\n            label: {\\n                GenreView(genre: genre)\\n                    .frame(minWidth: 250, maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)\\n                    .background(Color.overlay)\\n                    .cornerRadius(15)\\n            })\\n\\n    }\\n}\\n\\nstruct GenreLinkView_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        Group {\\n\\n            GenreLinkView(genre: GenreViewModel.placeholder)\\n                .frame(width: 250, height: 70, alignment: .center)\\n                .previewLayout(.sizeThatFits)\\n\\n            GenreLinkView(genre: GenreViewModel.placeholder)\\n                .frame(width: 250, height: 70, alignment: .center)\\n                .colorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson12UpdateHomeMyLibrarySectionHeader1\\n// $$liquidcoderFilename-HomeMyLibrarySectionHeader\\n\\nimport SwiftUI\\n\\nstruct HomeMyLibrarySectionHeader: View {\\n\\n    @Binding var selected: String\\n\\n    var body: some View {\\n        HStack {\\n            MyLibraryGenreList(selected: $selected)\\n\\n            SeeAllLink(destination: {\\n                // highlight-start\\n                PodcastListScreen(genre: selected)\\n                // highlight-end\\n            }).padding()\\n\\n        }.padding(.vertical, 10)\\n        .background(Color.background)\\n    }\\n}\\n\\nstruct HomeMyLibrarySectionHeader_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            HomeMyLibrarySectionHeader(selected: .constant(\\\"\\\"))\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            HomeMyLibrarySectionHeader(selected: .constant(\\\"\\\"))\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n    }\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson12Intro-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey guys, welcome to lesson #12. In the previous one, we finished creating the home screen, so we are going to do in this one is to create destination screens for all our navigation links we have in the app so far, let's jump right into it.\")), mdx(\"div\", {\n    id: \"Lesson12AllGenresScreen0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"AllGenres Screen\"), mdx(\"p\", null, \"This will be the screen that the user will navigate to when he clicks on the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"seeAll\"), \" Button on the genres section.\")), mdx(\"div\", {\n    id: \"Lesson12AllGenresScreen1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now, create a new folder inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens\")), \" one named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"AllGenres,\")), \" and inside it add a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"AllGenresScreen.\")), mdx(\"hr\", null), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\"))), mdx(\"div\", {\n    id: \"Lesson12AllGenresScreen2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We bring in the store to fetch the genres with and declare the genres filename property. This name must much the file that it in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Static\")), \" folder without the \\\"\", mdx(\"em\", {\n    parentName: \"p\"\n  }, \".json\\\"\"), \".\")), mdx(\"div\", {\n    id: \"Lesson12AllGenresScreen3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"gridItems\"), \" property is used in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LazyVGrid\"), \" below. We then put the grid inside a scrollview to obviously enable scrolling.\"), mdx(\"p\", null, \"Let's create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"content\"), \" next.\")), mdx(\"div\", {\n    id: \"Lesson12AllGenresScreen4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the content, we switch on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"allGenres\"), \" property, and for each case we will return an appropriate view. \"), mdx(\"p\", null, \"We are surrounding the entire logic with a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Group\"), \" to prevent \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"XCode\"), \" from freaking out.\")), mdx(\"div\", {\n    id: \"Lesson12AllGenresScreen5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"loading\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"failure\"), \" cases we are doing the exact same thing we did in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastListContent\"), \" view using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RedactedListView\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"FailureView\"), \", but obviously specialised for genres.\")), mdx(\"div\", {\n    id: \"Lesson12AllGenresScreen6-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And last, we create the success content which  is a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ForEach\"), \" view looping over the genres array, and creating a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreLinkView\"), \" for each genre view model.\")), mdx(\"div\", {\n    id: \"Lesson12UpdateHomeSectionHeaderView1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the HomeSectionHeaderView\"), mdx(\"p\", null, \"Now that we have a screen to navigate to, let's replace the existing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" with the highlighted line.\"), mdx(\"hr\", null), mdx(\"p\", null, \"Now run the app, and you should be able to navigate to the screen you have created upon clicking the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"seeAll\"), \" button.\")), mdx(\"div\", {\n    id: \"Lesson12PodcastListScreen0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"PodcastList Screen\"), mdx(\"p\", null, \"This will be the screen that the user will navigate to when he clicks on the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"seeAll\"), \" Button on the my library section.\")), mdx(\"div\", {\n    id: \"Lesson12PodcastListScreen1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now, create a new folder inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens\")), \" one named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"PodcastList,\")), \" and inside it add a swiftUI file named PodcastList\", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Screen.\")), mdx(\"hr\", null), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\"))), mdx(\"div\", {\n    id: \"Lesson12PodcastListScreen2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We declare a genre property that will come from the calling site, and be used to fetch podcasts.\"), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"gridItems\"), \" is the same as what we've declare in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"AllGenres\"), \" screen. It's now being used by 3 different view which makes it  a good candidate to refactor, but we will do that later.\")), mdx(\"div\", {\n    id: \"Lesson12PodcastListScreen3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Last, but not least, we create a grid where the content is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastListContent\"), \" created in the previous lesson.\"), mdx(\"p\", null, \"Then in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \", we just surround it with scrollview\")), mdx(\"div\", {\n    id: \"Lesson12UpdateGenreLinkView1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now that we have a screen to navigate to, we will do the same thing we did in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeSectionHeaderView\"), \", let's replace the existing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" with the highlighted line.\"), mdx(\"hr\", null), mdx(\"p\", null, \"Now run the app, and you should be able to navigate to the screen you have created upon clicking a genre view in the genre section.\")), mdx(\"div\", {\n    id: \"Lesson12UpdateHomeMyLibrarySectionHeader1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We will do the same thing here as well. Just replace the existing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" with the highlighted line.\"), mdx(\"hr\", null), mdx(\"p\", null, \"And run the app, and you should be able to navigate to the screen you have created upon clicking the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"seeAll\"), \" button on the library section. This will fetch podcasts for the currently selected genre.\")), mdx(\"div\", {\n    id: \"Lesson12Outro-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And this bring lesson #12 to a close. See you in #13 where we will learn how to parse RSS Feeds. Happy coding guys.\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#allgenres-screen","title":"AllGenres Screen"},{"url":"#updating-the-homesectionheaderview","title":"Updating the HomeSectionHeaderView"},{"url":"#podcastlist-screen","title":"PodcastList Screen"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Hey guys, welcome to lesson #12. In the previous one, we finished creating the home screen, so we are going to do in this one is to create destination screens for all our navigation links we have in the app so far, let's jump right into it.\"), mdx(\"h2\", null, \"AllGenres Screen\"), mdx(\"p\", null, \"This will be the screen that the user will navigate to when he clicks on the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"seeAll\"), \" Button on the genres section.\"), mdx(\"p\", null, \"Now, create a new folder inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens\")), \" one named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"AllGenres,\")), \" and inside it add a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"AllGenresScreen.\")), mdx(\"hr\", null), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-AllGenresScreen\\n\\nimport SwiftUI\\n\\nstruct AllGenresScreen: View {\\n\\n    // highlight-start\\n    @EnvironmentObject private var store: Store\\n\\n    private let genresFilename = \\\"genres\\\"\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct AllGenresScreen_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n    // highlight-end\\n\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            AllGenresScreen()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n                .environmentObject(store)\\n            AllGenresScreen()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"We bring in the store to fetch the genres with and declare the genres filename property. This name must much the file that it in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Static\")), \" folder without the \\\"\", mdx(\"em\", {\n    parentName: \"p\"\n  }, \".json\\\"\"), \".\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-AllGenresScreen\\n\\nimport SwiftUI\\n\\nstruct AllGenresScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    private let genresFilename = \\\"genres\\\"\\n\\n    // highlight-start\\n    private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n    // highlight-end\\n\\n    var body: some View {\\n        // highlight-start\\n        ScrollView {\\n            LazyVGrid(columns: gridItems,\\n                      alignment: .leading,\\n                      spacing: 20,\\n                  content: { content })\\n        }.navigationTitle(\\\"All genres\\\")\\n        // highlight-end\\n    }\\n}\\n\\nstruct AllGenresScreen_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            AllGenresScreen()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n                .environmentObject(store)\\n            AllGenresScreen()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"p\", null, \"In the content, we switch on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"allGenres\"), \" property, and for each case we will return an appropriate view. \"), mdx(\"p\", null, \"We are surrounding the entire logic with a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Group\"), \" to prevent \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"XCode\"), \" from freaking out.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-AllGenresScreen\\n\\nimport SwiftUI\\n\\nstruct AllGenresScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    private let genresFilename = \\\"genres\\\"\\n\\n    private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n\\n    private var content: some View {\\n        Group {\\n            switch store.apiState.allGenres {\\n            case .loading:\\n                // highlight-start\\n                RedactedListView(viewType: .genre)\\n                    .onAppear(perform: fetchGenres)\\n            // highlight-end\\n            case .success(let genres):\\n\\n            case .failure:\\n                // highlight-start\\n                FailureView(message: \\\"Unable to load genres\\\", showIcon: true, retryAction: fetchGenres)\\n                // highlight-end\\n            }\\n        }\\n    }\\n\\n    var body: some View {\\n        ScrollView {\\n            LazyVGrid(columns: gridItems,\\n                      alignment: .leading,\\n                      spacing: 20,\\n                  content: { content })\\n        }.navigationTitle(\\\"All genres\\\")\\n    }\\n\\n    // highlight-start\\n    private func fetchGenres() {\\n        store.dispatch(.api(.fetchGenres(genresFilename)))\\n    }\\n    // highlight-end\\n\\n}\\n\\nstruct AllGenresScreen_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            AllGenresScreen()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n                .environmentObject(store)\\n            AllGenresScreen()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"p\", null, \"In \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"loading\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"failure\"), \" cases we are doing the exact same thing we did in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastListContent\"), \" view using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RedactedListView\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"FailureView\"), \", but obviously specialised for genres.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-AllGenresScreen\\n\\nimport SwiftUI\\n\\nstruct AllGenresScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    private let genresFilename = \\\"genres\\\"\\n\\n    private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n\\n    private var content: some View {\\n        Group {\\n            switch store.apiState.allGenres {\\n            case .loading:\\n                RedactedListView(viewType: .genre)\\n                    .onAppear(perform: fetchGenres)\\n            case .success(let genres):\\n            // highlight-start\\n                createSuccess(genres: genres)\\n            // highlight-end\\n            case .failure:\\n                FailureView(message: \\\"Unable to load genres\\\", showIcon: true, retryAction: fetchGenres)\\n            }\\n        }\\n    }\\n\\n    var body: some View {\\n        ScrollView {\\n            LazyVGrid(columns: gridItems,\\n                      alignment: .leading,\\n                      spacing: 20,\\n                      content: { content })\\n        }.navigationTitle(\\\"All genres\\\")\\n    }\\n\\n    // highlight-start\\n    private func createSuccess(genres: [GenreViewModel]) -> some View {\\n        ForEach(genres) { genre in\\n            GenreLinkView(genre: genre)\\n                .frame(height: 70)\\n        }.padding(.horizontal)\\n    }\\n    // highlight-end\\n\\n    private func fetchGenres() {\\n        store.dispatch(.api(.fetchGenres(genresFilename)))\\n    }\\n}\\n\\nstruct AllGenresScreen_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            AllGenresScreen()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n                .environmentObject(store)\\n            AllGenresScreen()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"p\", null, \"And last, we create the success content which  is a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ForEach\"), \" view looping over the genres array, and creating a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreLinkView\"), \" for each genre view model.\"), mdx(\"h2\", null, \"Updating the HomeSectionHeaderView\"), mdx(\"p\", null, \"Now that we have a screen to navigate to, let's replace the existing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" with the highlighted line.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-HomeSectionHeaderView\\n\\nimport SwiftUI\\n\\nstruct HomeSectionHeaderView: View {\\n    var title: String\\n\\n    var body: some View {\\n        HStack {\\n            Text(title)\\n                .font(.title2)\\n                .bold()\\n            Spacer()\\n\\n            SeeAllLink(destination: {\\n                // highlight-start\\n                AllGenresScreen()\\n                // highlight-end\\n            }).padding(.vertical, 7)\\n            .cornerRadius(10)\\n            .background(Color.clear)\\n            .contentShape(RoundedRectangle(cornerRadius: 10, style: .continuous))\\n            .hoverEffect()\\n\\n        }.padding(.horizontal)\\n    }\\n}\\n\")), mdx(\"h2\", null, \"PodcastList Screen\"), mdx(\"p\", null, \"This will be the screen that the user will navigate to when he clicks on the seeAll Button on the my library section.\"), mdx(\"p\", null, \"Now, create a new folder inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens\")), \" one named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"PodcastList,\")), \" and inside it add a swiftUI file named PodcastList\", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Screen.\")), mdx(\"hr\", null), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-PodcastListScreen\\n\\nimport SwiftUI\\n\\nstruct PodcastListScreen: View {\\n\\n    // highlight-start\\n    var term: String\\n    // highlight-end\\n\\n    // highlight-start\\n    private var grid: some View {\\n\\n        LazyVGrid(columns: GridItems.shared,\\n                  alignment: .leading,\\n                  spacing: 10,\\n                  content: { PodcastListContent(term: term) })\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        ScrollView {\\n            grid\\n        }.navigationTitle(term)\\n        // highlight-end\\n    }\\n\\n}\\n\\nstruct PodcastListScreen_Previews: PreviewProvider {\\n\\n    static var previews: some View {\\n        Group {\\n            PodcastListScreen(term: \\\"technology\\\")\\n                .preferredColorScheme(.dark)\\n\\n            PodcastListScreen(term: \\\"technology\\\")\\n\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"We declare a genre property that will come from the calling site, and be used to fetch podcasts.\"), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"gridItems\"), \" is the same as what we've declare in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"AllGenres\"), \" screen. It's now being used by 3 different view which makes it  a good candidate to refactor, but we will do that later.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-GenreLinkView\\n\\nimport SwiftUI\\n\\nstruct GenreLinkView: View {\\n\\n    var genre: GenreViewModel\\n\\n    var body: some View {\\n\\n        NavigationLink(\\n            // highlight-start\\n            destination: PodcastListScreen(term: genre.name),\\n            // highlight-end\\n            label: {\\n                GenreView(genre: genre)\\n                    .frame(minWidth: 250, maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)\\n                    .background(Color.overlay)\\n                    .cornerRadius(15)\\n            })\\n\\n    }\\n}\\n\")), mdx(\"p\", null, \"Now that we have a screen to navigate to, we will do the same thing we did in the HomeSectionHeaderView, let's replace the existing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" with the highlighted line.\"), mdx(\"p\", null, \"Now run the app, and you should be able to navigate to the screen you have created upon clicking a genre view in the genre section.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-HomeMyLibrarySectionHeader\\n\\nimport SwiftUI\\n\\nstruct HomeMyLibrarySectionHeader: View {\\n\\n    @Binding var selected: String\\n\\n    var body: some View {\\n        HStack {\\n\\n            MyLibraryGenreList(selected: $selected)\\n\\n            SeeAllLink(destination: {\\n                // highlight-start\\n                PodcastListScreen(term: selected)\\n                // highlight-end\\n            }).padding()\\n            .background( Color.background )\\n        }.padding(.vertical, 10)\\n        .background(Color.background)\\n    }\\n}\\n\")), mdx(\"p\", null, \"We will do the same thing here as well. Just replace the existing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" with the highlighted line.\"), mdx(\"p\", null, \"And run the app, and you should be able to navigate to the screen you have created upon clicking the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"seeAll\"), \" button on the library section. This will fetch podcasts for the currently selected genre.\"), mdx(\"p\", null, \"And this bring lesson #12 to a close. See you in #13 where we will learn how to parse RSS Feeds. Happy coding guys.\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#allgenres-screen","title":"AllGenres Screen"},{"url":"#updating-the-homesectionheaderview","title":"Updating the HomeSectionHeaderView"},{"url":"#podcastlist-screen","title":"PodcastList Screen"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-02-26T09:23:40.272Z","isNewLayout":true,"previews":null,"slug":"lesson-13-rss-feed-parsing","title":"Lesson #13: RSS feed Parsing","updatedDate":"2021-02-26T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZ2bMuXZCA8aSiafqjSeqcegwpyI9QxdXFk7","type":"zip","id":"6def1763-7e81-54e1-a7a3-82db03068574","name":"Source code"},{"url":"https://www.donnywals.com/using-promises-and-futures-in-combine/","type":"Link","id":"de9c731c-8d26-51c2-9b3a-c22408f1251f","name":"Promises and futures in combine"}],"allChapters":null,"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson13RssServiceProtocol1\\n// $$liquidcoderFilename-RssServiceProtocol\\n\\nimport Foundation\\n// highlight-start\\nimport FeedKit\\nimport Combine\\n// highlight-end\\n\\n// highlight-start\\nprotocol RssServiceProtocol {\\n    func publisher(urlString: String) -> AnyPublisher<Feed, ParserError>\\n}\\n// highlight-end\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson13RssService3\\n// $$liquidcoderFilename-RssService\\n\\nimport Foundation\\n// highlight-start\\nimport Combine\\nimport FeedKit\\n// highlight-end\\n\\n// highlight-start\\nclass RssService: RssServiceProtocol {\\n\\n    func publisher(urlString: String) -> AnyPublisher<Feed, ParserError> {\\n\\n    }\\n}\\n// highlight-end\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson13RssService4\\n// $$liquidcoderFilename-RssService\\n\\nimport Foundation\\nimport Combine\\nimport FeedKit\\n\\nclass RssService: RssServiceProtocol {\\n\\n    func publisher(urlString: String) -> AnyPublisher<Feed, ParserError> {\\n        // highlight-start\\n        guard let url = URL(string: urlString) else  {\\n            return Fail(error: ParserError.internalError(reason: \\\"Invalid url\\\") ).eraseToAnyPublisher()\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson13RssService5\\n// $$liquidcoderFilename-RssService\\n\\nimport Foundation\\nimport Combine\\nimport FeedKit\\n\\nclass RssService: RssServiceProtocol {\\n\\n    func publisher(urlString: String) -> AnyPublisher<Feed, ParserError> {\\n        guard let url = URL(string: urlString) else  {\\n            return Fail(error: ParserError.internalError(reason: \\\"Invalid url\\\") ).eraseToAnyPublisher()\\n        }\\n\\n        // highlight-start\\n        let parser = FeedParser(URL: url)\\n        // highlight-end\\n\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson13RssService6\\n// $$liquidcoderFilename-RssService\\n\\nimport Foundation\\nimport Combine\\nimport FeedKit\\n\\nclass RssService: RssServiceProtocol {\\n\\n    func publisher(urlString: String) -> AnyPublisher<Feed, ParserError> {\\n        guard let url = URL(string: urlString) else  {\\n            return Fail(error: ParserError.internalError(reason: \\\"Invalid url\\\") ).eraseToAnyPublisher()\\n        }\\n\\n        let parser = FeedParser(URL: url)\\n\\n        // highlight-start\\n        return Future<Feed, ParserError> { promise in\\n\\n               }.eraseToAnyPublisher()\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson13RssService7\\n// $$liquidcoderFilename-RssService\\n\\nimport Foundation\\nimport Combine\\nimport FeedKit\\n\\nclass RssService: RssServiceProtocol {\\n\\n    func publisher(urlString: String) -> AnyPublisher<Feed, ParserError> {\\n        guard let url = URL(string: urlString) else  {\\n            return Fail(error: ParserError.internalError(reason: \\\"Invalid url\\\") ).eraseToAnyPublisher()\\n        }\\n\\n        let parser = FeedParser(URL: url)\\n\\n        return Future<Feed, ParserError> { promise in\\n            // highlight-start\\n            parser.parseAsync(queue: DispatchQueue.global(qos: .userInitiated)) { result in\\n                switch result {\\n                    case .success(let feed):\\n                        promise(.success(feed))\\n                    case .failure(let error):\\n                        promise(.failure(error))\\n                }\\n            }\\n            // highlight-end\\n        }.eraseToAnyPublisher()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson13UpdateAction1\\n// $$liquidcoderFilename-Action\\n\\nimport Foundation\\n\\nenum Action {\\n    enum Api {\\n        case fetchPodcasts(_ term: String, limit: Int = 10)\\n        case fetchGenres(_ filename: String)\\n        // highlight-start\\n        case updatePodcast(_ podcast: PodcastViewModel)\\n        // highlight-end\\n    }\\n\\n    enum Coredata {\\n        case fetchGenres\\n        case saveGenres(_ genres: [GenreViewModel])\\n    }\\n\\n    case api(_ action: Api)\\n    case coredata(_ action: Coredata)\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson13UpdateAppState1\\n// $$liquidcoderFilename-AppState\\n\\nimport Foundation\\nimport SwiftUI\\n\\nenum AppState {\\n\\n    enum Result<T: Equatable>: Equatable {\\n       ...\\n    }\\n\\n    struct  Api {\\n        var preferredPodcasts: [String: Result<[PodcastViewModel]>] = [:]\\n        var allGenres: Result<[GenreViewModel]> = .loading\\n        // highlight-start\\n        var podcast: Result<PodcastViewModel> = .loading\\n        // highlight-end\\n\\n    }\\n\\n    struct Coredata {\\n        var savedGenres: [GenreViewModel] = []\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson13UpdateAppEnvironment1\\n// $$liquidcoderFilename-AppEnvironment\\n\\nimport Foundation\\n\\nstruct AppEnvironment {\\n    let decoder: JSONDecoder\\n    let api: ApiServiceProtocol\\n    let coredata: CoredataServiceProtocol\\n    // highlight-start\\n    let rss: RssServiceProtocol\\n    // highlight-end\\n\\n    init(decoder: JSONDecoder = .init(),\\n         api: ApiServiceProtocol = ApiService(),\\n         coredata: CoredataServiceProtocol = CoredataService(),\\n         // highlight-start\\n         rss: RssServiceProtocol = RssService())\\n    // highlight-end\\n    {\\n        self.api = api\\n        self.decoder = decoder\\n        self.coredata = coredata\\n        // highlight-start\\n        self.rss = rss\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson13UpdateApiReducer1\\n// $$liquidcoderFilename-ApiReducer\\n\\nimport Foundation\\nimport Combine\\n\\nstruct ApiReducer {\\n\\n    typealias StateCompletion = ((AppState.Api) -> Void)\\n\\n    // MARK: - execute\\n    static func execute(action: Action.Api, state: AppState.Api, environment: AppEnvironment, completion:  @escaping StateCompletion ) -> AnyCancellable {\\n\\n        switch action {\\n        case .fetchPodcasts(let term, let limit):\\n            let publisher = environment.api.remotePublisher(term: term, limit: limit)\\n            return subscribe(to: publisher, type: PodcastList.self, environment: environment) { result, _ in\\n               setPreferredPodcasts(state: state, term: term, result: result, completion: completion)\\n            }\\n\\n        case .fetchGenres(let filename):\\n            let publisher = environment.api.localPublisher(filename: filename)\\n            return subscribe(to: publisher, type: GenreList.self, environment: environment) { result, _ in\\n                setAllGenres(state: state, result: result, completion: completion)\\n            }\\n        // highlight-start\\n        case .updatePodcast(let podcast):\\n            break\\n        // highlight-end\\n        }\\n\\n    }\\n    ...\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson13UpdateApiReducer2\\n// $$liquidcoderFilename-ApiReducer\\n\\nimport Foundation\\nimport Combine\\n\\nstruct ApiReducer {\\n    typealias StateCompletion = ((AppState.Api) -> Void)\\n\\n    static func execute(action: Action.Api, state: AppState.Api, environment: AppEnvironment, completion:  @escaping StateCompletion ) -> AnyCancellable {\\n        switch action {\\n        case .fetchPodcasts(let term, let limit):\\n            let publisher = environment.api.remotePublisher(term: term, limit: limit)\\n\\n            return subscribe(to: publisher, type: PodcastList.self, environment: environment) { result, _ in\\n                      setPreferredPodcasts(state: state, term: term, result: result, completion: completion)\\n                   }\\n\\n        case .fetchGenres(let filename):\\n            let publisher = environment.api.localPublisher(filename: filename)\\n                    return subscribe(to: publisher, type: GenreList.self, environment: environment) { result, _ in\\n                        setAllGenres(state: state, result: result, completion: completion)\\n                    }\\n        case .updatePodcast(let podcast):\\n            break\\n        }\\n\\n    }\\n\\n    // highlight-start\\n    private static func update(_ podcast: PodcastViewModel, environment: AppEnvironment,  completion: @escaping ( PodcastViewModel, Error?) -> Void) -> AnyCancellable {\\n\\n    }\\n    // highlight-end\\n\\n    ...\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson13UpdateApiReducer3\\n// $$liquidcoderFilename-ApiReducer\\n\\nimport Foundation\\nimport Combine\\n\\nstruct ApiReducer {\\n    typealias StateCompletion = ((AppState.Api) -> Void)\\n\\n    static func execute(action: Action.Api, state: AppState.Api, environment: AppEnvironment, completion:  @escaping StateCompletion ) -> AnyCancellable {\\n        switch action {\\n        case .fetchPodcasts(let term, let limit):\\n            let publisher = environment.api.remotePublisher(term: term, limit: limit)\\n\\n            return subscribe(to: publisher, type: PodcastList.self, environment: environment) { result, _ in\\n                setPreferredPodcasts(state: state, term: term, result: result, completion: completion)\\n            }\\n\\n        case .fetchGenres(let filename):\\n            let publisher = environment.api.localPublisher(filename: filename)\\n            return subscribe(to: publisher, type: GenreList.self, environment: environment) { result, _ in\\n                setAllGenres(state: state, result: result, completion: completion)\\n            }\\n        case .updatePodcast(let podcast):\\n            break\\n        }\\n\\n    }\\n\\n    private static func update(_ podcast: PodcastViewModel, environment: AppEnvironment,  completion: @escaping ( PodcastViewModel, Error?) -> Void) -> AnyCancellable {\\n\\n        // highlight-start\\n        environment.rss.publisher(urlString: podcast.feedUrl)\\n            .receive(on: DispatchQueue.main)\\n        // highlight-end\\n    }\\n\\n    ...\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson13UpdateApiReducer4\\n// $$liquidcoderFilename-ApiReducer\\n\\nimport Foundation\\nimport Combine\\n// highlight-start\\nimport FeedKit\\n// highlight-end\\n\\nstruct ApiReducer {\\n    typealias StateCompletion = ((AppState.Api) -> Void)\\n\\n    static func execute(action: Action.Api, state: AppState.Api, environment: AppEnvironment, completion:  @escaping StateCompletion ) -> AnyCancellable {\\n        switch action {\\n        case .fetchPodcasts(let term, let limit):\\n            let publisher = environment.api.remotePublisher(term: term, limit: limit)\\n\\n            return subscribe(to: publisher, type: PodcastList.self, environment: environment) { result, _ in\\n                setPreferredPodcasts(state: state, term: term, result: result, completion: completion)\\n            }\\n\\n        case .fetchGenres(let filename):\\n            let publisher = environment.api.localPublisher(filename: filename)\\n            return subscribe(to: publisher, type: GenreList.self, environment: environment) { result, _ in\\n                setAllGenres(state: state, result: result, completion: completion)\\n            }\\n        case .updatePodcast(let podcast):\\n            break\\n        }\\n\\n    }\\n\\n    private static func update(_ podcast: PodcastViewModel, environment: AppEnvironment,  completion: @escaping ( PodcastViewModel, Error?) -> Void) -> AnyCancellable {\\n\\n        environment.rss.publisher(urlString: podcast.feedUrl)\\n            .receive(on: DispatchQueue.main)\\n            // highlight-start\\n            .sink { status in\\n                if case .failure(let error) = status {\\n                    completion(podcast, error)\\n                }\\n            } receiveValue: { feed in\\n                guard let rssFeed = feed.rssFeed  else {\\n                    completion(podcast, ParserError.internalError(reason: \\\"Invalid error\\\"))\\n                    return\\n                }\\n\\n                let updatedPodcast = PodcastViewModel(rssFeed, existingPodcast: podcast)\\n                completion(updatedPodcast, nil)\\n\\n            }\\n        // highlight-end\\n    }\\n\\n    ...\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson13UpdateApiReducer5\\n// $$liquidcoderFilename-ApiReducer\\n\\nimport Foundation\\nimport Combine\\nimport FeedKit\\n\\nstruct ApiReducer {\\n    typealias StateCompletion = ((AppState.Api) -> Void)\\n\\n    static func execute(action: Action.Api, state: AppState.Api, environment: AppEnvironment, completion:  @escaping StateCompletion ) -> AnyCancellable {\\n        switch action {\\n        case .fetchPodcasts(let term, let limit):\\n            let publisher = environment.api.remotePublisher(term: term, limit: limit)\\n\\n            return subscribe(to: publisher, type: PodcastList.self, environment: environment) { result, _ in\\n                setPreferredPodcasts(state: state, term: term, result: result, completion: completion)\\n            }\\n\\n        case .fetchGenres(let filename):\\n            let publisher = environment.api.localPublisher(filename: filename)\\n            return subscribe(to: publisher, type: GenreList.self, environment: environment) { result, _ in\\n                setAllGenres(state: state, result: result, completion: completion)\\n            }\\n        case .updatePodcast(let podcast):\\n            // highlight-start\\n            return update(podcast, environment: environment) { (podcast, error) in\\n                var copyState = state\\n                if error != nil {\\n                    copyState.podcast = .failure\\n                } else {\\n                    copyState.podcast = .success(podcast)\\n                }\\n            }\\n            // highlight-end\\n        }\\n\\n    }\\n\\n    private static func update(_ podcast: PodcastViewModel, environment: AppEnvironment,  completion: @escaping ( PodcastViewModel, Error?) -> Void) -> AnyCancellable {\\n\\n        environment.rss.publisher(urlString: podcast.feedUrl)\\n            .receive(on: DispatchQueue.main)\\n            .sink { status in\\n                if case .failure(let error) = status {\\n                    completion(podcast, error)\\n                }\\n            } receiveValue: { feed in\\n                guard let rssFeed = feed.rssFeed  else {\\n                    completion(podcast, ParserError.internalError(reason: \\\"Invalid error\\\"))\\n                    return\\n                }\\n\\n                let updatedPodcast = PodcastViewModel(rssFeed, existingPodcast: podcast)\\n                completion(updatedPodcast, nil)\\n\\n            }\\n    }\\n\\n    ...\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson13Intro-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey guys, welcome back. In the previous lesson, we created navigation screen for all the link, we currently have in app. In this one, we will focus on implementing the RSSService class, and linking it to the store. Let's go!\")), mdx(\"div\", {\n    id: \"Lesson13RssServiceProtocol0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"RssServiceProtocol\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Networking\")), \" folder , add a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"RssServiceProtocol.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS, macOS and watchOS Extension\"))), mdx(\"div\", {\n    id: \"Lesson13RssServiceProtocol1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We will use both \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Combine\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Feedkit\"), \" for parsing the RSS Feed, so we need to import their respective frameworks.\"), mdx(\"p\", null, \"Then we create the protocol itself containing just one method, and that's all we need.\")), mdx(\"div\", {\n    id: \"Lesson13RssService1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Let's now create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RssService\"), \" class that will conform to the protocol we have just created.\")), mdx(\"div\", {\n    id: \"Lesson13RssService2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Networking\")), \" folder, add a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"RssService.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS, macOS and watchOS Extension\"))), mdx(\"div\", {\n    id: \"Lesson13RssService3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Just like in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RssServiceProtocol\"), \", we need the aforementioned frameworks, then create a class conforming it to the that protocol which requires the implementation its single method.\")), mdx(\"div\", {\n    id: \"Lesson13RssService4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next, we first check whether we can create a valid url from the provided url string, if we can not, we return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Fail\"), \" publisher.\")), mdx(\"div\", {\n    id: \"Lesson13RssService5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next, we create a parser object passing in the url we have just constructed. This is the object that will do the entire parsing job.\")), mdx(\"div\", {\n    id: \"Lesson13RssService6-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Having the parser object, we then return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Future\"), \" object which is a publisher that eventually produces a single value and then finishes or fails.\"), mdx(\"hr\", null), mdx(\"p\", null, \"Learn more about futures and promises \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://www.donnywals.com/using-promises-and-futures-in-combine/\"\n  }), \"here\"))), mdx(\"div\", {\n    id: \"Lesson13RssService7-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Inside the future block, we call the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"parseAsync\"), \" on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"parser\"), \" object to start parsing the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"RSS\"), \" f\", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"eed\"), \". We will then get a result from that operation that we switch on to retrieve the feed or the error in case of a failure.\"), mdx(\"p\", null, \"Then, using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Future\"), \"'s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"promise\"), \" , we propagate the result down to the publisher's pipeline.\")), mdx(\"div\", {\n    id: \"Lesson13UpdateAction1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the Action\"), mdx(\"p\", null, \"Add the highlighted line in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Api\"), \" enum.\"), mdx(\"hr\", null), mdx(\"p\", null, \"We will need to update the podcast view model with parsed \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"RSS feed\"), \" when users want see more details about it.\")), mdx(\"div\", {\n    id: \"Lesson13UpdateAppState1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the AppState\"), mdx(\"p\", null, \"Add the highlighted line in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Api\"), \" struct.\"), mdx(\"hr\", null), mdx(\"p\", null, \"The podcast field with hold on the podcast detail.\")), mdx(\"div\", {\n    id: \"Lesson13UpdateAppEnvironment1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the AppEnvironment\"), mdx(\"p\", null, \"Add the highlighted lines in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppEnvironmeny\"), \" struct.\"), mdx(\"hr\", null), mdx(\"p\", null, \"We bring in the rss service that we instantiate in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"init\"), \" with a default value.\")), mdx(\"div\", {\n    id: \"Lesson13UpdateApiReducer1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the ApiReducer\"), mdx(\"p\", null, \"You must have an error right now, add the following to silence it. We will work on that case block next, so don't worry about the new error.\")), mdx(\"div\", {\n    id: \"Lesson13UpdateApiReducer2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"update\"), \" function is the place of action where we will do all the job subscribing to the RssPublisher and updating the podcast view model\")), mdx(\"div\", {\n    id: \"Lesson13UpdateApiReducer3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Using the rss service object in environment, we create a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"publisher\"), \" that we receive on the main thread.\")), mdx(\"div\", {\n    id: \"Lesson13UpdateApiReducer4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We need to import the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"FeedKit\"), \" framework first.\"), mdx(\"p\", null, \"We subscribe to the publisher using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"sink\"), \" operator, calling the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"completion\"), \" with the appropriate result depending on whether the operator succeeded or failed.\"), mdx(\"p\", null, \"In the last block, we create a new updated  podcast view model from the existing one and the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"rssFeed\"), \".\")), mdx(\"div\", {\n    id: \"Lesson13UpdateApiReducer5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"updatePodcast\"), \" case, replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"break\"), \" with the highlighted block.\"), mdx(\"hr\", null), mdx(\"p\", null, \"What we are doing here is updating the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" based on whether the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"error\"), \" is \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"nil\"), \" or not\")), mdx(\"div\", {\n    id: \"Lesson13Outro-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And this brings lesson # 13 to close, let's move to lesson #14 where we will start creating the detail screen. Happy coding!!!\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#rssserviceprotocol","title":"RssServiceProtocol"},{"url":"#updating-the-action","title":"Updating the Action"},{"url":"#updating-the-appstate","title":"Updating the AppState"},{"url":"#updating-the-appenvironment","title":"Updating the AppEnvironment"},{"url":"#updating-the-apireducer","title":"Updating the ApiReducer"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey guys, welcome back. In the previous lesson, we created navigation screen for all the link, we currently have in app. In this one, we will focus on implementing the RSSService class, and linking it to the store. Let's go!\"), mdx(\"h2\", null, \"RssServiceProtocol\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Networking\")), \" folder , add a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"RssServiceProtocol.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS, macOS and watchOS Extension\")), mdx(\"hr\", null), mdx(\"p\", null, \"We will use both \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Combine\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Feedkit\"), \" for parsing the RSS Feed, so we need to import their respective frameworks.\"), mdx(\"p\", null, \"Then we create the protocol itself containing just one method, and that's all we need.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson13RssServiceProtocol1\\n// $$liquidcoderFilename-RssServiceProtocol\\n\\nimport Foundation\\n// highlight-start\\nimport FeedKit\\nimport Combine\\n// highlight-end\\n\\n// highlight-start\\nprotocol RssServiceProtocol {\\n    func publisher(urlString: String) -> AnyPublisher<Feed, ParserError>\\n}\\n// highlight-end\\n\")), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Networking\")), \" folder , add a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"RssService.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS, macOS and watchOS Extension\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-RssService\\n\\nimport Foundation\\n// highlight-start\\nimport Combine\\nimport FeedKit\\n// highlight-end\\n\\n// highlight-start\\nclass RssService: RssServiceProtocol {\\n\\n    func publisher(urlString: String) -> AnyPublisher<Feed, ParserError> {\\n\\n    }\\n}\\n// highlight-end\\n\")), mdx(\"p\", null, \"Just like in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RssServiceProtocol\"), \", we need the aforementioned frameworks, then create a class conforming it to the that protocol which requires the implementation its single method.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-RssService\\n\\nimport Foundation\\nimport Combine\\nimport FeedKit\\n\\nclass RssService: RssServiceProtocol {\\n\\n    func publisher(urlString: String) -> AnyPublisher<Feed, ParserError> {\\n        // highlight-start\\n        guard let url = URL(string: urlString) else  {\\n            return Fail(error: ParserError.internalError(reason: \\\"Invalid url\\\") ).eraseToAnyPublisher()\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"Next, we first check whether we can create a valid url from the provided url string, if we can not, we return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Fail\"), \" publisher.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-RssService\\n\\nimport Foundation\\nimport Combine\\nimport FeedKit\\n\\nclass RssService: RssServiceProtocol {\\n\\n    func publisher(urlString: String) -> AnyPublisher<Feed, ParserError> {\\n        guard let url = URL(string: urlString) else  {\\n            return Fail(error: ParserError.internalError(reason: \\\"Invalid url\\\") ).eraseToAnyPublisher()\\n        }\\n\\n        // highlight-start\\n        let parser = FeedParser(URL: url)\\n        // highlight-end\\n\\n    }\\n}\\n\")), mdx(\"p\", null, \"Next, we create a parser object passing in the url we have just constructed. This is the object that will do the entire parsing job.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-RssService\\n\\nimport Foundation\\nimport Combine\\nimport FeedKit\\n\\nclass RssService: RssServiceProtocol {\\n\\n    func publisher(urlString: String) -> AnyPublisher<Feed, ParserError> {\\n        guard let url = URL(string: urlString) else  {\\n            return Fail(error: ParserError.internalError(reason: \\\"Invalid url\\\") ).eraseToAnyPublisher()\\n        }\\n\\n        let parser = FeedParser(URL: url)\\n\\n        // highlight-start\\n        return Future<Feed, ParserError> { promise in\\n\\n        }.eraseToAnyPublisher()\\n        // highlight-end\\n\\n    }\\n}\\n\")), mdx(\"p\", null, \"Having the parser object, we then return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Future\"), \" object which is a publisher that eventually produces a single value and then finishes or fails .\"), mdx(\"hr\", null), mdx(\"p\", null, \"Learn more about futures and promises \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://www.donnywals.com/using-promises-and-futures-in-combine/\"\n  }), \"here\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-RssService\\n\\nimport Foundation\\nimport Combine\\nimport FeedKit\\n\\nclass RssService: RssServiceProtocol {\\n\\n    func publisher(urlString: String) -> AnyPublisher<Feed, ParserError> {\\n        guard let url = URL(string: urlString) else  {\\n            return Fail(error: ParserError.internalError(reason: \\\"Invalid url\\\") ).eraseToAnyPublisher()\\n        }\\n\\n        let parser = FeedParser(URL: url)\\n\\n        return Future<Feed, ParserError> { promise in\\n            // highlight-start\\n            parser.parseAsync(queue: DispatchQueue.global(qos: .userInitiated)) { result in\\n                switch result {\\n                    case .success(let feed):\\n                        promise(.success(feed))\\n                    case .failure(let error):\\n                        promise(.failure(error))\\n                }\\n            }\\n            // highlight-end\\n        }.eraseToAnyPublisher()\\n    }\\n}\\n\")), mdx(\"p\", null, \"Inside the future block, we call the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"parseAsync\"), \" on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"parser\"), \" object to start parsing the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"RSS\"), \" f\", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"eed\"), \". We will then get a result from that operation that we switch on to retrieve the feed or the error in case of a failure.\"), mdx(\"p\", null, \"Then, using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Future\"), \"'s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"promise\"), \" , we propagate the result down to the publisher's pipeline.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson13PreviewRssService1\\n// $$liquidcoderFilename-PreviewRssService\\n\\nimport Foundation\\n// highlight-start\\nimport Combine\\nimport FeedKit\\n// highlight-end\\n\\n// highlight-start\\nclass PreviewRssService: RssServiceProtocol {\\n\\n    func publisher(urlString: String) -> AnyPublisher<Feed, ParserError> {\\n\\n        guard let url = URL(string: urlString) else  {\\n            return Fail(error: ParserError.internalError(reason: \\\"Invalid error\\\") ).eraseToAnyPublisher()\\n        }\\n\\n        let parser = FeedParser(URL: url)\\n\\n        return Future<Feed, ParserError> { promise in\\n            parser.parseAsync(queue: DispatchQueue.global(qos: .userInitiated)) { result in\\n                switch result {\\n                    case .success(let feed):\\n                        promise(.success(feed))\\n                    case .failure(let error):\\n                        promise(.failure(error))\\n                }\\n            }\\n\\n        }.eraseToAnyPublisher()\\n    }\\n}\\n// highlight-end\\n\")), mdx(\"h2\", null, \"Updating the Action\"), mdx(\"p\", null, \"Add the highlighted line in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Api\"), \" enum.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-Action\\n\\nimport Foundation\\n\\nenum Action {\\n    enum Api {\\n        case fetchPodcasts(_ term: String, limit: Int = 10)\\n        case fetchGenres(_ filename: String)\\n        // highlight-start\\n        case updatePodcast(_ podcast: PodcastViewModel)\\n        // highlight-end\\n    }\\n\\n    enum Coredata {\\n        case fetchGenres\\n        case saveGenres(_ genres: [GenreViewModel])\\n    }\\n\\n    case api(_ action: Api)\\n    case coredata(_ action: Coredata)\\n\\n}\\n\")), mdx(\"p\", null, \"We will need to update the podcast view model with parsed \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"RSS feed\"), \" when users want see more details about it.\"), mdx(\"h2\", null, \"Updating the AppState\"), mdx(\"p\", null, \"Add the highlighted line in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Api\"), \" struct.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-AppState\\n\\nimport Foundation\\nimport SwiftUI\\n\\nenum AppState {\\n\\n    enum Result<T: Equatable>: Equatable {\\n       ...\\n    }\\n\\n    struct  Api {\\n        var preferredPodcasts: [String: Result<[PodcastViewModel]>] = [:]\\n        var allGenres: Result<[GenreViewModel]> = .loading\\n        // highlight-start\\n        var podcast: Result<PodcastViewModel> = .loading\\n        // highlight-end\\n\\n    }\\n\\n    struct Coredata {\\n        var savedGenres: [GenreViewModel] = []\\n    }\\n}\\n\")), mdx(\"p\", null, \"The podcast field with hold on the podcast detail.\"), mdx(\"h2\", null, \"Updating the AppEnvironment\"), mdx(\"p\", null, \"Add the highlighted lines in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppEnvironmeny\"), \" struct.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-AppEnvironment\\n\\nimport Foundation\\n\\nstruct AppEnvironment {\\n    let decoder: JSONDecoder\\n    let api: ApiServiceProtocol\\n    let coredata: CoredataServiceProtocol\\n    // highlight-start\\n    let rss: RssServiceProtocol\\n    // highlight-end\\n\\n    init(decoder: JSONDecoder = .init(),\\n         api: ApiServiceProtocol = ApiService(),\\n         coredata: CoredataServiceProtocol = CoredataService(),\\n         // highlight-start\\n         rss: RssServiceProtocol = RssService())\\n    // highlight-end\\n    {\\n        self.api = api\\n        self.decoder = decoder\\n        self.coredata = coredata\\n        // highlight-start\\n        self.rss = rss\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"h2\", null, \"Updating the ApiReducer\"), mdx(\"p\", null, \"You must have an error right now, add the following to silence it. We will work on that case next.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson13ApiReducer1\\n// $$liquidcoderFilename-ApiReducer\\n\\nimport Foundation\\nimport Combine\\n\\nstruct ApiReducer {\\n\\n    typealias StateCompletion = ((AppState.Api) -> Void)\\n\\n    // MARK: - execute\\n    static func execute(action: Action.Api, state: AppState.Api, environment: AppEnvironment, completion:  @escaping StateCompletion ) -> AnyCancellable {\\n\\n        switch action {\\n        case .fetchPodcasts(let term, let limit):\\n            let publisher = environment.api.remotePublisher(term: term, limit: limit)\\n            return subscribe(to: publisher, type: PodcastList.self, environment: environment) { result, _ in\\n               setPreferredPodcasts(state: state, term: term, result: result, completion: completion)\\n            }\\n\\n        case .fetchGenres(let filename):\\n            let publisher = environment.api.localPublisher(filename: filename)\\n            return subscribe(to: publisher, type: GenreList.self, environment: environment) { result, _ in\\n                setAllGenres(state: state, result: result, completion: completion)\\n            }\\n        // highlight-start\\n        case .updatePodcast(let podcast):\\n            break\\n        // highlight-end\\n        }\\n\\n    }\\n...\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson13UpdateApiReducer2\\n// $$liquidcoderFilename-ApiReducer\\n\\nimport Foundation\\nimport Combine\\n\\nstruct ApiReducer {\\n    typealias StateCompletion = ((AppState.Api) -> Void)\\n\\n    static func execute(action: Action.Api, state: AppState.Api, environment: AppEnvironment, completion:  @escaping StateCompletion ) -> AnyCancellable {\\n        switch action {\\n        case .fetchPodcasts(let term, let limit):\\n            let publisher = environment.api.remotePublisher(term: term, limit: limit)\\n\\n            return subscribe(to: publisher, type: PodcastList.self, environment: environment) { result, _ in\\n                      setPreferredPodcasts(state: state, term: term, result: result, completion: completion)\\n                   }\\n\\n        case .fetchGenres(let filename):\\n            let publisher = environment.api.localPublisher(filename: filename)\\n                    return subscribe(to: publisher, type: GenreList.self, environment: environment) { result, _ in\\n                        setAllGenres(state: state, result: result, completion: completion)\\n                    }\\n        case .updatePodcast(let podcast):\\n            break\\n        }\\n\\n    }\\n\\n    // highlight-start\\n    private static func update(_ podcast: PodcastViewModel, environment: AppEnvironment,  completion: @escaping ( PodcastViewModel, Error?) -> Void) -> AnyCancellable {\\n\\n    }\\n    // highlight-end\\n\\n        ...\\n\")), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"update\"), \" function is the place of action where we will do all the job subscribing to the RssPublisher and updating the podcast view model\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-ApiReducer\\n\\nimport Foundation\\nimport Combine\\n\\nstruct ApiReducer {\\n    typealias StateCompletion = ((AppState.Api) -> Void)\\n\\n        ...\\n\\n    private static func update(_ podcast: PodcastViewModel, environment: AppEnvironment,  completion: @escaping ( PodcastViewModel, Error?) -> Void) -> AnyCancellable {\\n\\n        // highlight-start\\n        environment.rss.publisher(urlString: podcast.feedUrl)\\n            .receive(on: DispatchQueue.main)\\n        // highlight-end\\n    }\\n\\n        ...\\n}\\n\")), mdx(\"p\", null, \"Using the rss service object in environment, we create a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"publisher\"), \" that we receive on the main thread.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-ApiReducer\\n\\nimport Foundation\\nimport Combine\\n// highlight-start\\nimport FeedKit\\n// highlight-end\\n\\nstruct ApiReducer {\\n    typealias StateCompletion = ((AppState.Api) -> Void)\\n\\n        ...    \\n    private static func update(_ podcast: PodcastViewModel, environment: AppEnvironment,  completion: @escaping ( PodcastViewModel, Error?) -> Void) -> AnyCancellable {\\n\\n        environment.rss.publisher(urlString: podcast.feedUrl)\\n            .receive(on: DispatchQueue.main)\\n            // highlight-start\\n            .sink { status in\\n                if case .failure(let error) = status {\\n                    completion(podcast, error)\\n                }\\n            } receiveValue: { feed in\\n                guard let rssFeed = feed.rssFeed  else {\\n                    completion(podcast, ParserError.internalError(reason: \\\"Invalid error\\\"))\\n                    return\\n                }\\n\\n                let updatedPodcast = PodcastViewModel(rssFeed, existingPodcast: podcast)\\n                completion(updatedPodcast, nil)\\n\\n            }\\n        // highlight-end\\n    }\\n\\n        ...\\n}\\n\")), mdx(\"p\", null, \"We need to import the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"FeedKit\"), \" framework first.\"), mdx(\"p\", null, \"We subscribe to the publisher using the sink operator, calling the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"completion\"), \" with the appropriate result depending on whether the operator succeeded or failed.\"), mdx(\"p\", null, \"In the last block, we create a new updated  podcast view model from the existing one and the rssFeed.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-ApiReducer\\n\\nimport Foundation\\nimport Combine\\nimport FeedKit\\n\\nstruct ApiReducer {\\n    typealias StateCompletion = ((AppState.Api) -> Void)\\n\\n    static func execute(action: Action.Api, state: AppState.Api, environment: AppEnvironment, completion:  @escaping StateCompletion ) -> AnyCancellable {\\n        switch action {\\n        case .fetchPodcasts(let term, let limit):\\n            let publisher = environment.api.remotePublisher(term: term, limit: limit)\\n\\n            return subscribe(to: publisher, type: PodcastList.self, environment: environment) { result, _ in\\n                setPreferredPodcasts(state: state, term: term, result: result, completion: completion)\\n            }\\n\\n        case .fetchGenres(let filename):\\n            let publisher = environment.api.localPublisher(filename: filename)\\n            return subscribe(to: publisher, type: GenreList.self, environment: environment) { result, _ in\\n                setAllGenres(state: state, result: result, completion: completion)\\n            }\\n        case .updatePodcast(let podcast):\\n            // highlight-start\\n            return update(podcast, environment: environment) { (podcast, error) in\\n                var copyState = state\\n                if error != nil {\\n                    copyState.podcast = .failure\\n                } else {\\n                    copyState.podcast = .success(podcast)\\n                }\\n            }\\n            // highlight-end\\n        }\\n\\n    }\\n        ...\\n}\\n\")), mdx(\"p\", null, \"Now in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"updatePodcast\"), \" case, replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"break\"), \" with the highlighted block.\"), mdx(\"hr\", null), mdx(\"p\", null, \"What we are doing here is updating the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" based on whether the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"error\"), \" is \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"nil\"), \" or not\"), mdx(\"p\", null, \"And this brings lesson # 13 to close, let's move to lesson #14 where we will start creating the detail screen. Happy coding.\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#rssserviceprotocol","title":"RssServiceProtocol"},{"url":"#updating-the-action","title":"Updating the Action"},{"url":"#updating-the-appstate","title":"Updating the AppState"},{"url":"#updating-the-appenvironment","title":"Updating the AppEnvironment"},{"url":"#updating-the-apireducer","title":"Updating the ApiReducer"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-03-01T09:17:02.474Z","isNewLayout":true,"previews":{"previews":"{\n\t\"Lesson14PodcastDetailScreen1\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\t\"Lesson14PodcastDetailScreen2\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\t\"Lesson14PodcastDetailScreen3\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\t\"Lesson14PodcastDetailScreen4\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614502830/StreamCast/lesson%2014/cpwlcote54nueesd3srv.png\"],\n\t\"Lesson14PodcastDetailElement1\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\t\"Lesson14PodcastDetailElement2\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\t\"Lesson14PodcastDetailElement3\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614503339/StreamCast/lesson%2014/z7lllcwolk8lepxvmwas.png\"],\n\t\"Lesson14PodcastDetailHeaderComponent1\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\t\"Lesson14PodcastDetailHeaderComponent2\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\t\"Lesson14PodcastDetailHeaderComponent4\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614502830/StreamCast/lesson%2014/qeneedzduegsodgsdp3f.png\"],\n\t\"Lesson14PodcastDetailGenreCollection1\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\t\"Lesson14PodcastDetailGenreCollection2\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614502830/StreamCast/lesson%2014/pyy0crky92ttpxei2ddu.png\"],\n\t\"Lesson14PodcastDetailHeaderComponent4\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614502830/StreamCast/lesson%2014/qeneedzduegsodgsdp3f.png\"],\n\t\"Lesson14PodcastDetailContent1\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\t\"Lesson14PodcastDetailContent4\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614502830/StreamCast/lesson%2014/xcm5bglpnp2paryyedhq.png\"],\n\t\"Lesson14UpdatePodcastDetailScreen1\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614502830/StreamCast/lesson%2014/dutvnxmual2daqkvshbg.png\"]\n}"},"slug":"lesson-14-podcast-detail-screen-part-1","title":"Lesson #14: Podcast Detail Screen Part 1","updatedDate":"2021-03-01T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZwvUuXZkVwxqMzET10E60gt72qTsFCKR3Gk","type":"zip","id":"f792c2df-d1ae-542d-a5bc-ba9892f2ef30","name":"Source code"}],"allChapters":null,"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailScreen0\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614502830/StreamCast/lesson%2014/dutvnxmual2daqkvshbg.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614502830/StreamCast/lesson%2014/dutvnxmual2daqkvshbg.png\"\n  }))))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailScreen1\\n// $$liquidcoderFilename-PodcastDetailScreen\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailScreen: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct PodcastDetailScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        PodcastDetailScreen()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailScreen2\\n// $$liquidcoderFilename-PodcastDetailScreen\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailScreen: View {\\n\\n    // highlight-start\\n    var podcast: PodcastViewModel\\n    // highlight-end\\n\\n    // highlight-start\\n    @EnvironmentObject private var store: Store\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct PodcastDetailScreen_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n    // highlight-end\\n\\n    static var previews: some View {\\n\\n        // highlight-start\\n        Group {\\n            PodcastDetailScreen(podcast: PodcastViewModel.placeholder)\\n                .colorScheme(.dark)\\n            PodcastDetailScreen(podcast: PodcastViewModel.placeholder)\\n        }.environmentObject(store)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailScreen3\\n// $$liquidcoderFilename-PodcastDetailScreen\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailScreen: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    @EnvironmentObject private var store: Store\\n    // highlight-start\\n    @State private var state: AppState.Result<PodcastViewModel> = .loading\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n\\n    // highlight-start\\n    private func checkState() {\\n        if case .success(let podcast) = store.apiState.podcast {\\n            if podcast.title == self.podcast.title {\\n                state = store.apiState.podcast\\n            } else {\\n                store.dispatch(.api(.updatePodcast(podcast)))\\n            }\\n        }\\n    }\\n    // highlight-end\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailScreen4\\n// $$liquidcoderFilename-PodcastDetailScreen\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailScreen: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    @EnvironmentObject private var store: Store\\n    @State private var state: AppState.Result<PodcastViewModel> = .loading\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        ZStack {\\n            switch state {\\n            case .loading:\\n                Text(\\\"Loading...\\\")\\n            case .success(let podcast):\\n                Text(podcast.title)\\n            case .failure:\\n                FailureView(message: \\\"Unable to fetch podcast\\\", retryAction: checkState)\\n            }\\n        }.onAppear {\\n            checkState()\\n        }.onChange(of: store.apiState.podcast) { _ in\\n            checkState()\\n        }\\n        // highlight-end\\n    }\\n\\n    private func checkState() {\\n        if case .success(let podcast) = store.apiState.podcast,\\n           podcast.title == self.podcast.title {\\n                state = store.apiState.podcast\\n        } else {\\n            store.dispatch(.api(.updatePodcast(podcast)))\\n        }\\n    }\\n}\\n\")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailElement0\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614503158/StreamCast/lesson%2014/vquo8jk7szmcx1yslrnk.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614503158/StreamCast/lesson%2014/vquo8jk7szmcx1yslrnk.png\"\n  }))))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailElement1\\n// $$liquidcoderFilename-PodcastDetailElement\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailElement: View {\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct PodcastDetailElement_Previews: PreviewProvider {\\n    static var previews: some View {\\n        PodcastDetailElement()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailElement2\\n// $$liquidcoderFilename-PodcastDetailElement\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailElement: View {\\n\\n    // highlight-start\\n    var name: String\\n    var value: String\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct PodcastDetailElement_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        // highlight-start\\n        Group {\\n            PodcastDetailElement(name: \\\"Author\\\", value: \\\"Liquidcoder\\\")\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PodcastDetailElement(name: \\\"Author\\\", value: \\\"Liquidcoder\\\")\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailElement3\\n// $$liquidcoderFilename-PodcastDetailElement\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailElement: View {\\n\\n    var name: String\\n    var value: String\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        HStack(alignment: .top) {\\n            Text(name + \\\" :\\\")\\n                .foregroundColor(.gray)\\n            Spacer()\\n            Text(value.isEmpty ? \\\"Unknown\\\" : value)\\n                .bold()\\n        }\\n        // highlight-end\\n    }\\n}\\n\\nstruct PodcastDetailElement_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        Group {\\n            PodcastDetailElement(name: \\\"Author\\\", value: \\\"Liquidcoder\\\")\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PodcastDetailElement(name: \\\"Author\\\", value: \\\"Liquidcoder\\\")\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailHeaderComponent0\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614503521/StreamCast/lesson%2014/iwgvu3wjq2ucty7z2u1d.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614503521/StreamCast/lesson%2014/iwgvu3wjq2ucty7z2u1d.png\"\n  }))))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailHeaderComponent1\\n// $$liquidcoderFilename-PodcastDetailHeaderComponent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailHeaderComponent: View {\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct HeaderDetailComponent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        PodcastDetailHeaderComponent()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailHeaderComponent2\\n// $$liquidcoderFilename-PodcastDetailHeaderComponent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailHeaderComponent: View {\\n\\n    // highlight-start\\n    var podcast: PodcastViewModel\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct HeaderDetailComponent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            PodcastDetailHeaderComponent(podcast: .placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PodcastDetailHeaderComponent(podcast: .placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailHeaderComponent3\\n// $$liquidcoderFilename-PodcastDetailHeaderComponent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailHeaderComponent: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    // highlight-start\\n    var title: some View {\\n        Text(podcast.title)\\n            .font(.largeTitle)\\n            .bold()\\n            .fixedSize(horizontal: false, vertical: true)\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        // highlight-start\\n        VStack(alignment: .leading, spacing: 20) {\\n\\n            title\\n\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailHeaderComponent4\\n// $$liquidcoderFilename-PodcastDetailHeaderComponent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailHeaderComponent: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    var title: some View {\\n        ...\\n    }\\n\\n    // highlight-start\\n    var details: some View {\\n        VStack(alignment: .leading, spacing: 10)  {\\n            HeaderDetailElement(name: \\\"Author\\\", value: podcast.author)\\n            HeaderDetailElement(name: \\\"Publisher\\\", value: podcast.publisher)\\n            HeaderDetailElement(name: \\\"Explicit\\\", value: podcast.explicitContent)\\n            HeaderDetailElement(name: \\\"Release Date\\\", value: podcast.date)\\n            HeaderDetailElement(name: \\\"Language\\\", value: podcast.language ?? \\\"\\\")\\n        }\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        VStack(alignment: .leading, spacing: 20) {\\n\\n            title\\n            // highlight-start\\n            details\\n            // highlight-end\\n\\n            // highlight-start\\n        }.frame(maxWidth: .infinity, alignment: .topLeading)\\n        .padding()\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailGenreCollection0\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614503746/StreamCast/lesson%2014/wqhledptma4leidkcyql.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614503746/StreamCast/lesson%2014/wqhledptma4leidkcyql.png\"\n  }))))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailGenreCollection1\\n// $$liquidcoderFilename-PodcastDetailGenreCollection\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailGenreCollection: View {\\n\\n    var body: some View {\\n\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct PodcastDetailGenreCollection_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            PodcastDetailGenreCollection(genres: [\\\"Technology\\\", \\\"NFL\\\", \\\"Comedy\\\", \\\"Politics\\\", \\\"Geography\\\"])\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PodcastDetailGenreCollection(genres: [\\\"Technology\\\", \\\"NFL\\\", \\\"Comedy\\\", \\\"Politics\\\", \\\"Geography\\\"])\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailGenreCollection2\\n// $$liquidcoderFilename-PodcastDetailGenreCollection\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailGenreCollection: View {\\n\\n    // highlight-start\\n    var genres: [String]\\n    // highlight-en\\n\\n    // highlight-start\\n    private let gridItems: [GridItem] = Array(repeating: .init(.flexible(), spacing: 5), count: 3)\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        LazyVGrid(columns: gridItems, alignment: .center, spacing: 10) {\\n            ForEach(genres, id: \\\\.self) { genre in\\n                Text(genre)\\n                    .font(.caption)\\n                    .foregroundColor(Color.pop)\\n                    .frame(maxWidth: .infinity)\\n                    .frame(height: 15)\\n                    .padding(10)\\n                    .background(Color.pop.opacity(0.3))\\n                    .cornerRadius(20)\\n\\n            }.frame(maxHeight: .infinity, alignment: .top)\\n        }\\n        // highlight-end\\n    }\\n}\\n\\nstruct PodcastDetailGenreCollection_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            PodcastDetailGenreCollection(genres: [\\\"Technology\\\", \\\"NFL\\\", \\\"Comedy\\\", \\\"Politics\\\", \\\"Geography\\\"])\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PodcastDetailGenreCollection(genres: [\\\"Technology\\\", \\\"NFL\\\", \\\"Comedy\\\", \\\"Politics\\\", \\\"Geography\\\"])\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14UpdatePodcastViewModel1\\n// $$liquidcoderFilename-PodcastViewModel\\n\\nimport Foundation\\nimport FeedKit\\n\\nstruct PodcastViewModel: Identifiable, Equatable {\\n\\n        ...\\n\\n    private static func createPlaceholder(id: Int) -> PodcastViewModel {\\n        PodcastViewModel(id: id,\\n                         title: \\\"Lorem ipsum\\\",\\n                         // highlight-start\\n                         imageUrl: \\\"https://via.placeholder.com/150/2EB9F0/000000?Text=liquidcoder.com\\\",\\n                         // highlight-end\\n                         author: \\\"Lorem ipsum\\\",\\n                         totalEpisodes: 23,\\n                         explicitContent: \\\"No\\\",\\n                         description: \\\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostr\\\",\\n                         country: \\\"US\\\",\\n                         // highlight-start\\n                         genres: [\\\"Technology\\\", \\\"NFL\\\", \\\"Comedy\\\", \\\"Politics\\\", \\\"Geography\\\"],\\n                        // highlight-end\\n                         nextEpisodePubDate: Date() ,\\n                         feedUrl: \\\"\\\")\\n    }\\n\\n    ...\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailHeaderComponent4\\n// $$liquidcoderFilename-PodcastDetailHeaderComponent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailHeaderComponent: View {\\n\\n        ...\\n\\n    // highlight-start\\n    var genres: some View {\\n        VStack {\\n            Divider()\\n            PodcastDetailGenreCollection(genres: podcast.genres)\\n            Divider()\\n        }\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        VStack(alignment: .leading, spacing: 20) {\\n\\n            title\\n            details\\n            // highlight-start\\n            genres\\n            // highlight-end\\n\\n        }.frame(maxWidth: .infinity, alignment: .topLeading)\\n        .padding()\\n    }\\n}\\n\")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailContent0\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614503926/StreamCast/lesson%2014/qk5d5ougp8frcc7eryd3.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614503926/StreamCast/lesson%2014/qk5d5ougp8frcc7eryd3.png\"\n  }))))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailContent1\\n// $$liquidcoderFilename-PodcastDetailContent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailContent: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct PodcastDetailContent_Previews: PreviewProvider {\\n\\n    static var previews: some View {\\n\\n        // highlight-start\\n        Group {\\n            PodcastDetailContent(podcast: PodcastViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PodcastDetailContent(podcast: PodcastViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-start\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailContent2\\n// $$liquidcoderFilename-PodcastDetailContent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailContent: View {\\n\\n    // highlight-start\\n    var podcast: PodcastViewModel\\n    // highlight-end\\n\\n    var body: some View {\\n        // highlight-start\\n        GeometryReader { geometry in\\n            ScrollView(showsIndicators: false, content: {\\n\\n                createImage(size: geometry.size.width)\\n\\n            })\\n        }\\n        // highlight-end\\n    }\\n\\n    // highlight-start\\n    private func createImage(size: CGFloat) -> some View {\\n        SCImage(podcast: podcast, size: size, cornerRadius: 0)\\n            .aspectRatio(contentMode: .fill)\\n\\n    }\\n    // highlight-end\\n\\n}\\n\\nstruct PodcastDetailContent_Previews: PreviewProvider { ... }\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailContent3\\n// $$liquidcoderFilename-PodcastDetailContent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailContent: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    var body: some View {\\n        GeometryReader { geometry in\\n            ScrollView(showsIndicators: false, content: {\\n                // highlight-start\\n                VStack {\\n                    // highlight-end\\n\\n                    createImage(size: geometry.size.width)\\n\\n                    // highlight-start\\n                    VStack(alignment: .leading, spacing: 10) {\\n                        PodcastDetailHeaderComponent(podcast: podcast)\\n                    }\\n                    // highlight-end\\n\\n                    // highlight-start\\n                }\\n                // highlight-end\\n            })\\n        }\\n    }\\n\\n    private func createImage(size: CGFloat) -> some View {\\n       ...\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailContent4\\n// $$liquidcoderFilename-PodcastDetailContent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailContent: View {\\n\\n    var podcast: PodcastViewModel\\n\\n        // highlight-start\\n    private var description: some View {\\n        Text(podcast.description)\\n            .padding(.horizontal)\\n            .fixedSize(horizontal: false, vertical: true)\\n    }\\n        // highlight-end\\n\\n    var body: some View {\\n        GeometryReader { geometry in\\n            ScrollView(showsIndicators: false, content: {\\n                VStack {\\n                    createImage(size: geometry.size.width)\\n\\n                    VStack(alignment: .leading, spacing: 10) {\\n                        PodcastDetailHeaderComponent(podcast: podcast)\\n                        // highlight-start\\n                        description\\n                        // highlight-end\\n                    }\\n                }\\n// highlight-start\\n            }).navigationBarTitle(\\\"\\\", displayMode: .inline)\\n// highlight-end\\n        }\\n    }\\n\\n    private func createImage(size: CGFloat) -> some View {\\n                ...\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14UpdatePodcastDetailScreen1\\n// $$liquidcoderFilename-PodcastDetailScreen\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailScreen: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    @EnvironmentObject private var store: Store\\n    @State private var state: AppState.Result<PodcastViewModel> = .loading\\n\\n    var body: some View {\\n\\n        ZStack {\\n            switch state {\\n            case .loading:\\n                // highlight-start\\n                ProgressView(\\\"Loading...\\\")\\n                // highlight-end\\n            case .success(let podcast):\\n                // highlight-start\\n                PodcastDetailContent(podcast: podcast)\\n            // highlight-end\\n            case .failure:\\n                FailureView(message: \\\"Unable to fetch podcast\\\", retryAction: checkState)\\n            }\\n        }.onAppear {\\n            checkState()\\n        }.onChange(of: store.apiState.podcast) { _ in\\n            checkState()\\n        }\\n    }\\n\\n    private func checkState() {\\n        if case .success(let podcast) = store.apiState.podcast,\\n           podcast.title == self.podcast.title {\\n                state = store.apiState.podcast\\n        } else {\\n            store.dispatch(.api(.updatePodcast(podcast)))\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14UpdateApiReducer1\\n// $$liquidcoderFilename-ApiReducer\\n\\nimport Foundation\\nimport Combine\\nimport FeedKit\\n\\nstruct ApiReducer {\\n    typealias StateCompletion = ((AppState.Api) -> Void)\\n\\n    static func execute(action: Action.Api, state: AppState.Api, environment: AppEnvironment, completion:  @escaping StateCompletion ) -> AnyCancellable {\\n        switch action {\\n        case .fetchPodcasts(let term, let limit):\\n            let publisher = environment.api.remotePublisher(term: term, limit: limit)\\n\\n            return subscribe(to: publisher, type: PodcastList.self, environment: environment) { result, _ in\\n                setPreferredPodcasts(state: state, term: term, result: result, completion: completion)\\n            }\\n\\n        case .fetchGenres(let filename):\\n            let publisher = environment.api.localPublisher(filename: filename)\\n            return subscribe(to: publisher, type: GenreList.self, environment: environment) { result, _ in\\n                setAllGenres(state: state, result: result, completion: completion)\\n            }\\n        case .updatePodcast(let podcast):\\n            return update(podcast, environment: environment) { (podcast, error) in\\n                var copyState = state\\n                if error != nil {\\n                    copyState.podcast = .failure\\n                } else {\\n                    copyState.podcast = .success(podcast)\\n                }\\n                // highlight-start\\n                completion(copyState)\\n                // highlight-end\\n\\n            }\\n        }\\n    }\\n\\n        ...\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14UpdatePodcastLinkView1\\n// $$liquidcoderFilename-PodcastLinkView\\n\\nimport SwiftUI\\n\\nstruct PodcastLinkView: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    var body: some View {\\n\\n        VStack {\\n            // highlight-start\\n            NavigationLink(destination: PodcastDetailScreen(podcast: podcast) ) {\\n                // highlight-end\\n                PodcastView(podcast: podcast)\\n            }\\n\\n            Divider()\\n        }\\n    }\\n}\\n\\nstruct PodcastLinkView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            PodcastLinkView(podcast: PodcastViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PodcastLinkView(podcast: PodcastViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson14Introduction-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey, welcome back to another lesson. In the previous one, we implemented the RSS Feed parser service, so let's put it to work now. \")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailScreen0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"PodcastDetail Screen\"), mdx(\"p\", null, \"This screen will be the main detail screen where users will navigate once they click on one of the podcast items in the list.\")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailScreen1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens\")), \" folder, create another folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"PodcastDetail\")), \", and inside it add a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PodcastListScreen.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\"))), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailScreen2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"This is a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailScreen\"), \", so it's obvious that we need a podcast view model that will come from the calling site.\"), mdx(\"p\", null, \"And we also need the store to make  various operations.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"As always we use our preview store in the preview provider.\"))), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailScreen3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We need to keep track of a local state of the result because the one that's in the store is global, hence the declaration of that state property above.\"), mdx(\"p\", null, \"We then create that \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"checkState\"), \" function that will check whether the global state is a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"success\"), \" which means we have a podcast view model in memory, if yes we proceed to  the next check where we verify whether the podcast that was tapped is the same as the one in memory (the global one), if yes, we update the local state, otherwise we need to fetch the new podcast details and update the global state.\")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailScreen31-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The benefit of doing this is to prevent loading the same podcast multiple times if the user taps on a single podcast item several times in a row.\")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailScreen4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We will be listening to the local state rather than the global one as we have been doing in other views, so we switch on it, and for the first cases (\", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"loading\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"success\"), \"), we return placeholder \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" views, of course we will replace those later on.\"), mdx(\"p\", null, \"We want to check the state when the screen appears as well as when the global podcast state changes in order to update the local one if need be\")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailScreen41-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We will come back to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailScreen\"), \" view later, right now we need to create \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"components\"), \" and \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"elements\"), \" that will compose it\")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailElement0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"PodcastDetail Element\"), mdx(\"p\", null, \"This will be the view that will represent a single element in the details row above the genres collection description.\")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailElement1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now, in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"PodcastDetail\")), \" folder, add another folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Elements\")), \", and inside it create a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PodcastDetailElement\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\"))), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailElement2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We only need 2 properties here, the name will be on the left and the value on the right as seen in the picture shown.\")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailElement3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We align the 2 values horizontally, we want to display \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"\\\"Unknown\\\"\"), \" when the value is empty. \"), mdx(\"p\", null, \"And that's our detail element.\")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailHeaderComponent0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"PodcastDetailHeader Component\"), mdx(\"p\", null, \"This component will content a list of  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailElement\"), \" we have just created as well as the title and genres.\")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailHeaderComponent1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now, in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"PodcastDetail\")), \" folder, add another folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Components\")), \", and inside it create a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PodcastDetailHeaderComponent\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\"))), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailHeaderComponent2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We bring in the podcast view model that will come from the parent \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailScreen\"), \".\")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailHeaderComponent3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We create the title view first that we make \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"bold\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"large\"), \". The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fixedSize\"), \" will prevent it  from being cut off if it's too long.\"), mdx(\"p\", null, \"I have written an overview article of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" view component with its associated modifiers that you can find \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://liquidcoder.com/blog/\"\n  }), \"here\"), \" if you want to know more about it.\")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailHeaderComponent4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next, we create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"details\"), \" view which is a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" containing  various details about a particular podcast.\"), mdx(\"p\", null, \"We make it fill the available width and aligned to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"topLeading\"), \" which is the origin of the parent.\")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailHeaderComponent41-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We will take a quick detour from the podcast detail header component to build the genre collection component.\")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailGenreCollection0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"PodcastDetailGenreCollection Component\"), mdx(\"p\", null, \"This is the component that will go directly below the details in the header component.\")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailGenreCollection1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now, in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"PodcastDetail \\u2192 Components\")), \" folder, add  a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PodcastDetailGenreCollection\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\"))), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailGenreCollection2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We declare the properties that we will need to build the grid.\"), mdx(\"p\", null, \"In \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \", we create a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LazyVGrid\"), \" using the genres array as elements, and for each genre we just return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" view styled properly.\")), mdx(\"div\", {\n    id: \"Lesson14UpdatePodcastViewModel1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the PodcastViewModel Struct\"), mdx(\"p\", null, \"Add the 2 lines in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createPlaceholder\"), \" function, we will need to display the images in the previews as well.\")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailHeaderComponent40-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We have everything we need to finish the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailHeaderComponent\"), \" view, let's do that now.\")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailHeaderComponent4-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Finish off the PodcastDetailHeader Component\"), mdx(\"p\", null, \"We then create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"genres\"), \" view which wraps the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailGenreCollection\"), \" inside a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" , surrounded by 2 \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Divider\"), \" views.\"), mdx(\"p\", null, \"Next we add it in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" below \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"details\"), \".\")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailContent0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"PodcastDetailContent Component\"), mdx(\"p\", null, \"This where we will put all the components and elements together, and this is the component that will be returned in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"success\"), \" block of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailScreen\"), \".\")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailContent1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now, in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"PodcastDetail \\u2192 Components\")), \" folder, add  a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PodcastDetailContent\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\"))), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailContent2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \", we surround the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Scrollview\"), \" with a the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GeometryReader\"), \" Container because we need to know the size that our view occupies in order to fit the image properly.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"I have publisher an overview article about the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GeometryReader\"), \" container, check it out \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://liquidcoder.com/article/geometryreader-overview\"\n  }), \"here\"), \" if you want to know more.\"))), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailContent3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next, we put the image in a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" container before adding the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailHeaderComponent\"), \" below it.\")), mdx(\"div\", {\n    id: \"Lesson14PodcastDetailContent4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Last, we create the description view that we then add inside the inner \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" container below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailHeaderComponent\"), \".\"), mdx(\"p\", null, \"Let's now update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailScreen\"), \".\")), mdx(\"div\", {\n    id: \"Lesson14UpdatePodcastDetailScreen1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailScreen\"), \", replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" in the loading block with a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ProgressView\"), \", this will create an activity indicator.\"), mdx(\"p\", null, \"And the success block, replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" with our newly created \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailContent\"), \".\")), mdx(\"div\", {\n    id: \"Lesson14UpdateApiReducer1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Small update to the ApiReducer\"), mdx(\"p\", null, \"In the last lesson, I forgot to send back the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"copyState\"), \" after updating it, so add the following line to do it.\")), mdx(\"div\", {\n    id: \"Lesson14UpdatePodcastLinkView1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Linking the PodcastDetailScreen\"), mdx(\"p\", null, \"Now, open the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastLinkView\"), \", and replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" in the destination parameter with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailScreen\"), \". Build and run the project, then tap any podcast view, and you should be able to navigate to its details.\")), mdx(\"div\", {\n    id: \"Lesson14Outro-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Some podcasts with fail to load their details, but don't worry we will fix those later.\"), mdx(\"p\", null, \"And this bring lesson #14 to a close. See you in  lesson #15 where we will create the episode list screen.\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#podcastdetail-screen","title":"PodcastDetail Screen"},{"url":"#podcastdetail-element","title":"PodcastDetail Element"},{"url":"#podcastdetailheader-component","title":"PodcastDetailHeader Component"},{"url":"#podcastdetailgenrecollection-component","title":"PodcastDetailGenreCollection Component"},{"url":"#updating-the-podcastviewmodel-struct","title":"Updating the PodcastViewModel Struct"},{"url":"#finish-off-the-podcastdetailheader-component","title":"Finish off the PodcastDetailHeader Component"},{"url":"#podcastdetailcontent-component","title":"PodcastDetailContent Component"},{"url":"#small-update-to-the-apireducer","title":"Small update to the ApiReducer"},{"url":"#linking-the-podcastdetailscreen","title":"Linking the PodcastDetailScreen"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey, welcome back to another lesson. In the previous one, we implemented the RSS service, so let's put it to work now. \"), mdx(\"h2\", null, \"PodcastDetail Screen\"), mdx(\"p\", null, \"This screen will be the main detail screen where users will navigateThis is a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailScreen\"), \", so it's obvious that we need a podcast view model that will come from the calling site. once they click on one of the podcast items in the list.\"), mdx(\"p\", null, \"This is a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailScreen\"), \", so it's obvious that we need a podcast view model that will come from the calling site.\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens\")), \" folder, create another folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"PodcastDetail\")), \", and inside it add a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PodcastListScreen.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-PodcastDetailScreen\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailScreen: View {\\n\\n    // highlight-start\\n    var podcast: PodcastViewModel\\n    // highlight-end\\n\\n    // highlight-start\\n    @EnvironmentObject private var store: Store\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct PodcastDetailScreen_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n    // highlight-end\\n\\n    static var previews: some View {\\n\\n        // highlight-start\\n        Group {\\n            PodcastDetailScreen(podcast: PodcastViewModel.placeholder)\\n                .colorScheme(.dark)\\n            PodcastDetailScreen(podcast: PodcastViewModel.placeholder)\\n        }.environmentObject(store)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"This is a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailScreen\"), \", so it's obvious that we need a podcast view model that will come from the calling site.\"), mdx(\"p\", null, \"And we also need the store to make  various operations.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"As always we use our preview store in the preview provider.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailScreen3\\n// $$liquidcoderFilename-PodcastDetailScreen\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailScreen: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    @EnvironmentObject private var store: Store\\n    // highlight-start\\n    @State private var state: AppState.Result<PodcastViewModel> = .loading\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n\\n    // highlight-start\\n    private func checkState() {\\n        if case .success(let podcast) = store.apiState.podcast {\\n            if podcast.title == self.podcast.title {\\n                state = store.apiState.podcast\\n            } else {\\n                store.dispatch(.api(.updatePodcast(podcast)))\\n            }\\n        }\\n    }\\n    // highlight-end\\n\\n}\\n\")), mdx(\"p\", null, \"We need to keep track of a local state of the result because the one that's in the store is global, hence the declaration of that state property above.\"), mdx(\"p\", null, \"We then create that \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"checkState\"), \" function that will check whether the global state is a success which means we have a podcast view model in memory, if yes we proceed to  the next check where we verify whether the podcast that was tapped is the same as the one in memory (the global one), if yes, we update the local state, otherwise we need to fetch the new podcast details and update the global state.\"), mdx(\"p\", null, \"The benefit of doing this is to prevent loading the same podcast multiple times if the user taps on a single podcast item several times in a row.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailScreen4\\n// $$liquidcoderFilename-PodcastDetailScreen\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailScreen: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    @EnvironmentObject private var store: Store\\n    @State private var state: AppState.Result<PodcastViewModel> = .loading\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        ZStack {\\n            switch state {\\n            case .loading:\\n                Text(\\\"Loading...\\\")\\n            case .success(let podcast):\\n                Text(podcast.title)\\n            case .failure:\\n                FailureView(message: \\\"Unable to fetch podcast\\\", retryAction: checkState)\\n            }\\n        }.onAppear {\\n            checkState()\\n        }.onChange(of: store.apiState.podcast) { _ in\\n            checkState()\\n        }\\n        // highlight-end\\n    }\\n\\n    private func checkState() {\\n        if case .success(let podcast) = store.apiState.podcast,\\n           podcast.title == self.podcast.title {\\n                state = store.apiState.podcast\\n        } else {\\n            store.dispatch(.api(.updatePodcast(podcast)))\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"We will be listening to the local state rather than the global one as we have been doing in other views, so we switch on it, and for the first cases (\", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"loading\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"success\"), \"), we return placeholder \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" views, of course we will replace those later on.\"), mdx(\"p\", null, \"We want to check the state when the screen appears as well as when the global podcast state changes in order to update the local one if need be\"), mdx(\"p\", null, \"We will come back to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailScreen\"), \" view later, right now we need to create \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"components\"), \" and \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"elements\"), \" that will compose it\"), mdx(\"h2\", null, \"PodcastDetail Element\"), mdx(\"p\", null, \"This will be the view that will represent a single element in the details row above the genres collection description.\"), mdx(\"p\", null, \"Now, in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"PodcastDetail\")), \" folder, add another folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Elements\")), \", and inside it create a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PodcastDetailElement\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailElement2\\n// $$liquidcoderFilename-PodcastDetailElement\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailElement: View {\\n\\n    // highlight-start\\n    var name: String\\n    var value: String\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct PodcastDetailElement_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        // highlight-start\\n        Group {\\n            PodcastDetailElement(name: \\\"Author\\\", value: \\\"Liquidcoder\\\")\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PodcastDetailElement(name: \\\"Author\\\", value: \\\"Liquidcoder\\\")\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"We only need 2 properties here, the name will be on the left and the value on the right as seen in the picture shown.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailElement3\\n// $$liquidcoderFilename-PodcastDetailElement\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailElement: View {\\n\\n    var name: String\\n    var value: String\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        HStack(alignment: .top) {\\n            Text(name + \\\" :\\\")\\n                .foregroundColor(.gray)\\n            Spacer()\\n            Text(value.isEmpty ? \\\"Unknown\\\" : value)\\n                .bold()\\n        }\\n        // highlight-end\\n    }\\n}\\n\\nstruct PodcastDetailElement_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        Group {\\n            PodcastDetailElement(name: \\\"Author\\\", value: \\\"Liquidcoder\\\")\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PodcastDetailElement(name: \\\"Author\\\", value: \\\"Liquidcoder\\\")\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"We align the 2 values horizontally, we want to display \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"\\\"Unknown\\\"\"), \" when the value is empty. \"), mdx(\"p\", null, \"And that's our detail element.\"), mdx(\"h2\", null, \"PodcastDetailHeader Component\"), mdx(\"p\", null, \"This component will content a list of \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailElement\"), \" we have just created as well as the title and genres.\"), mdx(\"p\", null, \"Now, in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"PodcastDetail\")), \" folder, add another folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Components\")), \", and inside it create a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PodcastDetailHeaderComponent\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailHeaderComponent2\\n// $$liquidcoderFilename-PodcastDetailHeaderComponent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailHeaderComponent: View {\\n\\n    // highlight-start\\n    var podcast: PodcastViewModel\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct HeaderDetailComponent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            PodcastDetailHeaderComponent(podcast: .placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PodcastDetailHeaderComponent(podcast: .placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"We bring in the podcast view model that will come from the parent \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailScreen\"), \".\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailHeaderComponent3\\n// $$liquidcoderFilename-PodcastDetailHeaderComponent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailHeaderComponent: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    // highlight-start\\n    var title: some View {\\n        Text(podcast.title)\\n            .font(.largeTitle)\\n            .bold()\\n            .fixedSize(horizontal: false, vertical: true)\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        // highlight-start\\n        VStack(alignment: .leading, spacing: 20) {\\n\\n            title\\n\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"We create the title view first that we make bold and large. The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fixedSize\"), \" will prevent it  from being cut off it's long.\"), mdx(\"p\", null, \"I have written an overview article of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" view component with its associated modifiers that you can find \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://liquidcoder.com/blog/\"\n  }), \"here\"), \" if you want to know more about it.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailHeaderComponent3\\n// $$liquidcoderFilename-PodcastDetailHeaderComponent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailHeaderComponent: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    var title: some View {\\n        ...\\n    }\\n\\n    // highlight-start\\n    var details: some View {\\n        VStack(alignment: .leading, spacing: 10)  {\\n            PodcastDetailElement(name: \\\"Author\\\", value: podcast.author)\\n            PodcastDetailElement(name: \\\"Publisher\\\", value: podcast.publisher)\\n            PodcastDetailElement(name: \\\"Explicit\\\", value: podcast.explicitContent)\\n            PodcastDetailElement(name: \\\"Release Date\\\", value: podcast.date)\\n            PodcastDetailElement(name: \\\"Language\\\", value: podcast.language ?? \\\"\\\")\\n        }\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        VStack(alignment: .leading, spacing: 20) {\\n\\n            title\\n            // highlight-start\\n            details\\n            // highlight-end\\n\\n            // highlight-start\\n        }.frame(maxWidth: .infinity, alignment: .topLeading)\\n        .padding()\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"Next, we create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"details\"), \" view which is a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" containing  various details about a particular podcast.\"), mdx(\"p\", null, \"We make it fill the available width and aligned to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"topLeading\"), \" which is (0, 0)\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailGenreCollection2\\n// $$liquidcoderFilename-PodcastDetailGenreCollection\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailGenreCollection: View {\\n\\n    // highlight-start\\n    var genres: [String]\\n    // highlight-en\\n\\n    // highlight-start\\n    private let gridItems: [GridItem] = Array(repeating: .init(.flexible(), spacing: 5), count: 3)\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        LazyVGrid(columns: gridItems, alignment: .center, spacing: 10) {\\n            ForEach(genres, id: \\\\.self) { genre in\\n                Text(genre)\\n                    .font(.caption)\\n                    .foregroundColor(Color.pop)\\n                    .frame(maxWidth: .infinity)\\n                    .frame(height: 15)\\n                    .padding(10)\\n                    .background(Color.pop.opacity(0.3))\\n                    .cornerRadius(20)\\n\\n            }.frame(maxHeight: .infinity, alignment: .top)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"We declare the properties that we will need to build the grid.\"), mdx(\"p\", null, \"In \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \", we create a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LazyVGrid\"), \" using the genres array as elements, and for each genre we just return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" view styled properly.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14UpdatePodcastViewModel1\\n// $$liquidcoderFilename-PodcastViewModel\\n\\nimport Foundation\\nimport FeedKit\\n\\nstruct PodcastViewModel: Identifiable, Equatable {\\n\\n    ...\\n\\n    private static func createPlaceholder(id: Int) -> PodcastViewModel {\\n        PodcastViewModel(id: id,\\n                         title: \\\"Lorem ipsum\\\",\\n                         // highlight-start\\n                         imageUrl: \\\"https://via.placeholder.com/150/2EB9F0/000000?Text=liquidcoder.com\\\",\\n                         // highlight-end\\n                         author: \\\"Lorem ipsum\\\",\\n                         totalEpisodes: 23,\\n                         explicitContent: \\\"No\\\",\\n                         description: \\\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostr\\\",\\n                         country: \\\"US\\\",\\n                         // highlight-start\\n                         genres: [\\\"Technology\\\", \\\"NFL\\\", \\\"Comedy\\\", \\\"Politics\\\", \\\"Geography\\\"],\\n                        // highlight-end\\n                         nextEpisodePubDate: Date() ,\\n                         feedUrl: \\\"\\\")\\n    }\\n\\n    ...\\n}\\n\")), mdx(\"p\", null, \"Add the 2 lines in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createPlaceholder\"), \" function, we will need to display the images in the previews as well.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailHeaderComponent4\\n// $$liquidcoderFilename-PodcastDetailHeaderComponent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailHeaderComponent: View {\\n\\n    ...\\n\\n    // highlight-start\\n    var genres: some View {\\n        VStack {\\n            Divider()\\n            PodcastDetailGenreCollection(genres: podcast.genres)\\n            Divider()\\n        }\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        VStack(alignment: .leading, spacing: 20) {\\n\\n            title\\n            details\\n            // highlight-start\\n            genres\\n            // highlight-end\\n\\n        }.frame(maxWidth: .infinity, alignment: .topLeading)\\n        .padding()\\n    }\\n}\\n\")), mdx(\"p\", null, \"We then create \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"genres\"), \" view which wraps the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailGenreCollection\"), \" inside a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" , surrounded by 2 \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Divider\"), \" views.\"), mdx(\"p\", null, \"We then add it in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" below \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"details\"), \".\"), mdx(\"h2\", null, \"PodcastDetailContent Component\"), mdx(\"p\", null, \"This where we will put all the components and elements together, and this is the component that will be returned in the success block of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailScreen\"), \".\"), mdx(\"p\", null, \"Now, in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"PodcastDetail \\u2192 Components\")), \" folder, add  a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PodcastDetailContent\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailContent2\\n// $$liquidcoderFilename-PodcastDetailContent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailContent: View {\\n\\n    // highlight-start\\n    var podcast: PodcastViewModel\\n    // highlight-end\\n\\n    var body: some View {\\n        // highlight-start\\n        GeometryReader { geometry in\\n            ScrollView(showsIndicators: false, content: {\\n                createImage(size: geometry.size.width)\\n            })\\n        }\\n        // highlight-end\\n    }\\n\\n    // highlight-start\\n    private func createImage(size: CGFloat) -> some View {\\n        SCImage(podcast: podcast, size: size, cornerRadius: 0)\\n            .aspectRatio(contentMode: .fill)\\n\\n    }\\n    // highlight-end\\n\\n}\\n\")), mdx(\"p\", null, \"In \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \", we surround the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Scrollview\"), \" with a the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GeometryReader\"), \" Container because we need to know the size that our view occupies in order to fit the image properly.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"I have publisher an overview article about the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GeometryReader\"), \" container, check it out \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://liquidcoder.com/article/geometryreader-overview\"\n  }), \"here\"), \" if you want to know more.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailContent3\\n// $$liquidcoderFilename-PodcastDetailContent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailContent: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    var body: some View {\\n        GeometryReader { geometry in\\n            ScrollView(showsIndicators: false, content: {\\n                // highlight-start\\n                VStack {\\n                    // highlight-end\\n                    createImage(size: geometry.size.width)\\n\\n                    // highlight-start\\n                    VStack(alignment: .leading, spacing: 10) {\\n                        PodcastDetailHeaderComponent(podcast: podcast)\\n                    }\\n                    // highlight-end\\n                    // highlight-start\\n                }\\n                // highlight-end\\n            })\\n        }\\n    }\\n\\n    private func createImage(size: CGFloat) -> some View {\\n       ...\\n    }\\n}\\n\")), mdx(\"p\", null, \"Next, we put the image in a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" container before adding the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailHeaderComponent\"), \" below it.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14PodcastDetailContent4\\n// $$liquidcoderFilename-PodcastDetailContent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailContent: View {\\n\\n    var podcast: PodcastViewModel\\n\\n        // highlight-start\\n    private var description: some View {\\n        Text(podcast.description)\\n            .padding(.horizontal)\\n            .fixedSize(horizontal: false, vertical: true)\\n    }\\n        // highlight-end\\n\\n    var body: some View {\\n        GeometryReader { geometry in\\n            ScrollView(showsIndicators: false, content: {\\n                VStack {\\n                    createImage(size: geometry.size.width)\\n\\n                    VStack(alignment: .leading, spacing: 10) {\\n                        PodcastDetailHeaderComponent(podcast: podcast)\\n                        // highlight-start\\n                        description\\n                        // highlight-end\\n                    }\\n                }\\n// highlight-start\\n            }).navigationBarTitle(\\\"\\\", displayMode: .inline)\\n// highlight-end\\n        }\\n    }\\n\\n    private func createImage(size: CGFloat) -> some View {\\n                ...\\n    }\\n}\\n\")), mdx(\"p\", null, \"Last, we create the description view that we then add inside the inner \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" container below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailHeaderComponent\"), \".\"), mdx(\"p\", null, \"Let's now update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailScreen\"), \".\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14UpdatePodcastDetailScreen1\\n// $$liquidcoderFilename-PodcastDetailScreen\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailScreen: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    @EnvironmentObject private var store: Store\\n    @State private var state: AppState.Result<PodcastViewModel> = .loading\\n\\n    var body: some View {\\n\\n        ZStack {\\n            switch state {\\n            case .loading:\\n                // highlight-start\\n                ProgressView(\\\"Loading...\\\")\\n                // highlight-end\\n            case .success(let podcast):\\n                // highlight-start\\n                PodcastDetailContent(podcast: podcast)\\n            // highlight-end\\n            case .failure:\\n                FailureView(message: \\\"Unable to fetch podcast\\\", retryAction: checkState)\\n            }\\n        }.onAppear {\\n            checkState()\\n        }.onChange(of: store.apiState.podcast) { _ in\\n            checkState()\\n        }\\n    }\\n\\n    private func checkState() {\\n        if case .success(let podcast) = store.apiState.podcast,\\n           podcast.title == self.podcast.title {\\n                state = store.apiState.podcast\\n        } else {\\n            store.dispatch(.api(.updatePodcast(podcast)))\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"Inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailScreen\"), \", replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" in the loading block with a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ProgressView\"), \", this will create an activity indicator.\"), mdx(\"p\", null, \"And in the success block, replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" with our newly created \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailContent\"), \".\"), mdx(\"h2\", null, \"Small update to the ApiReducer\"), mdx(\"p\", null, \"In the previous lesson, I forgot to send back the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"copyState\"), \" after updating it, so add the following line to do it.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14UpdateApiReducer1\\n// $$liquidcoderFilename-ApiReducer\\n\\nimport Foundation\\nimport Combine\\nimport FeedKit\\n\\nstruct ApiReducer {\\n    typealias StateCompletion = ((AppState.Api) -> Void)\\n\\n    static func execute(action: Action.Api, state: AppState.Api, environment: AppEnvironment, completion:  @escaping StateCompletion ) -> AnyCancellable {\\n        switch action {\\n        case .fetchPodcasts(let term, let limit):\\n            let publisher = environment.api.remotePublisher(term: term, limit: limit)\\n\\n            return subscribe(to: publisher, type: PodcastList.self, environment: environment) { result, _ in\\n                setPreferredPodcasts(state: state, term: term, result: result, completion: completion)\\n            }\\n\\n        case .fetchGenres(let filename):\\n            let publisher = environment.api.localPublisher(filename: filename)\\n            return subscribe(to: publisher, type: GenreList.self, environment: environment) { result, _ in\\n                setAllGenres(state: state, result: result, completion: completion)\\n            }\\n        case .updatePodcast(let podcast):\\n            return update(podcast, environment: environment) { (podcast, error) in\\n                var copyState = state\\n                if error != nil {\\n                    copyState.podcast = .failure\\n                } else {\\n                    copyState.podcast = .success(podcast)\\n                }\\n                // highlight-start\\n                completion(copyState)\\n                // highlight-end\\n\\n            }\\n        }\\n    }\\n\\n        ...\\n}\\n\")), mdx(\"h2\", null, \"Linking the PodcastDetailScreen\"), mdx(\"p\", null, \"Now, replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" in the destination parameter with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailScreen\"), \" , and build and run the project, then tap any podcast view, you should be able to navigate to its details.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14UpdatePodcastLinkView1\\n// $$liquidcoderFilename-PodcastLinkView\\n\\nimport SwiftUI\\n\\nstruct PodcastLinkView: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    var body: some View {\\n\\n        VStack {\\n            // highlight-start\\n            NavigationLink(destination: PodcastDetailScreen(podcast: podcast) ) {\\n                // highlight-end\\n                PodcastView(podcast: podcast)\\n            }\\n\\n            Divider()\\n        }\\n    }\\n}\\n\\nstruct PodcastLinkView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            PodcastLinkView(podcast: PodcastViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PodcastLinkView(podcast: PodcastViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"Some podcasts with fail to load their details, but don't worry we will fix those later.\"), mdx(\"p\", null, \"And this bring lesson #14 to a close. See you in  lesson #15 where we will create the episode list screen.\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#podcastdetail-screen","title":"PodcastDetail Screen"},{"url":"#podcastdetail-element","title":"PodcastDetail Element"},{"url":"#podcastdetailheader-component","title":"PodcastDetailHeader Component"},{"url":"#podcastdetailcontent-component","title":"PodcastDetailContent Component"},{"url":"#small-update-to-the-apireducer","title":"Small update to the ApiReducer"},{"url":"#linking-the-podcastdetailscreen","title":"Linking the PodcastDetailScreen"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-03-03T08:54:44.637Z","isNewLayout":true,"previews":{"previews":"{\n\"Lesson15UpdateMyLibraryGenreList1\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614676007/StreamCast/Lesson%2015/psrx1pobeaeyuromfole.png\"],\n\"Lesson15EpisodeListScreen1\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\"Lesson15EpisodeListScreen2\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\"Lesson15EpisodeListScreen4\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614676468/StreamCast/Lesson%2015/mzy1jpn1t3ce2z6a38mt.png\"],\n\"Lesson15EpisodeViewHeader1\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\"Lesson15EpisodeViewHeader2\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\"Lesson15EpisodeViewHeader3\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614677027/StreamCast/Lesson%2015/simiforbbaye01hfmiud.png\"],\n\"Lesson15EpisodeViewBody2\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\"Lesson15EpisodeViewBody3\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614677346/StreamCast/Lesson%2015/sui6rfxwcflhtwepbw32.png\"],\n\"Lesson15EpisodeView1\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\"Lesson15EpisodeView2\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614677547/StreamCast/Lesson%2015/ogll9kmsa8uefgq1wehu.png\"],\n\"Lesson15UpdatePodcastDetailContent1\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614677707/StreamCast/Lesson%2015/iceyreigrkumf1evh52i.png\"]\n}"},"slug":"lesson-15-podcast-detail-screen-part-2","title":"Lesson #15: Podcast Detail Screen part 2","updatedDate":"2021-03-03T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZDXXfXZnwuGtU9gyN7iF4Wd56tJmSz79WIX","type":"zip","id":"76e9b3e8-e783-5b26-8307-ee765ec693fd","name":"Source code"}],"allChapters":null,"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15UpdateMyLibraryGenreList1\\n// $$liquidcoderFilename-MyLibraryGenreList\\n\\nimport SwiftUI\\n\\nstruct MyLibraryGenreList: View {\\n\\n    @Binding var selected: String\\n\\n    @EnvironmentObject private var store: Store\\n    @State private var genres: [GenreViewModel] = []\\n\\n    var body: some View {\\n        ScrollView(.horizontal, showsIndicators: false, content: {\\n            ScrollViewReader { reader in\\n                createScrollViewContent(reader: reader)\\n            }\\n        }).padding(.vertical, 10)\\n        .background(Color.background)\\n        .onAppear {\\n            // highlight-start\\n            if store.coredataState.savedGenres.isEmpty {\\n                store.dispatch(.coredata(.fetchGenres))\\n            } else {\\n                genres = store.coredataState.savedGenres\\n            }\\n            // highlight-end\\n        }.onChange(of: store.coredataState.savedGenres, perform: { value in\\n            ...\\n        })\\n    }\\n\\n    fileprivate func createScrollViewContent(reader: ScrollViewProxy) -> some View {\\n        ...\\n    }\\n\\n    fileprivate func createButton(_ index: Int, reader: ScrollViewProxy) -> some View {\\n        ...\\n    }\\n}\\n\")), mdx(\"div\", {\n    id: \"Lesson15EpisodeListScreen0\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614676468/StreamCast/Lesson%2015/mzy1jpn1t3ce2z6a38mt.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614676468/StreamCast/Lesson%2015/mzy1jpn1t3ce2z6a38mt.png\"\n  }))))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15EpisodeListScreen1\\n// $$liquidcoderFilename-EpisodeListScreen\\n\\nimport SwiftUI\\n\\nstruct EpisodeListScreen: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct EpisodeListScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        EpisodeListScreen()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15EpisodeListScreen2\\n// $$liquidcoderFilename-EpisodeListScreen\\n\\nimport SwiftUI\\n\\nstruct EpisodeListScreen: View {\\n\\n    // highlight-start\\n    var episodes: [EpisodeViewModel]\\n    // highlight-end\\n\\n    // highlight-start\\n    enum ListSection: String, CaseIterable {\\n        case downloaded = \\\"Downloaded\\\"\\n        case allEpisodes = \\\"All episodes\\\"\\n    }\\n\\n    @State private var listSection: ListSection = .allEpisodes\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct EpisodeListScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        // highlight-start\\n        Group {\\n            EpisodeListScreen(episodes: EpisodeViewModel.listPlaceholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeListScreen(episodes: EpisodeViewModel.listPlaceholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15EpisodeListScreen3\\n// $$liquidcoderFilename-EpisodeListScreen\\n\\nimport SwiftUI\\n\\nstruct EpisodeListScreen: View {\\n\\n    ...\\n\\n   // highlight-start\\n\\n    private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n\\n    private var section: some View {\\n        Section(header: createSegmentedPicker()) {\\n            ForEach(episodes) { episode in\\n                EpisodeView(episode: episode, play: {})\\n            }\\n        }\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        // highlight-start\\n                ScrollView {\\n            LazyVGrid(columns: gridItems,\\n                      alignment: .leading,\\n                      spacing: 20,\\n                      content: { section }\\n            ).padding()\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15EpisodeListScreen4\\n// $$liquidcoderFilename-EpisodeListScreen\\n\\nimport SwiftUI\\n\\nstruct EpisodeListScreen: View {\\n\\n    ...\\n\\n    private var section: some View {\\n        Section(header: createSegmentedPicker()) {\\n            ForEach(episodes) { episode in\\n                EpisodeView(episode: episode, play: {})\\n            }\\n        }\\n    }\\n\\n    var body: some View {\\n\\n                ScrollView {\\n            LazyVGrid(columns: GridItems.shared,\\n                      alignment: .leading,\\n                      spacing: 20,\\n                      content: { section }\\n            ).padding()\\n        }\\n    }\\n\\n// highlight-start\\n    func createSegmentedPicker() -> some View {\\n        Picker(\\\"\\\", selection: $listSection) {\\n            Text(ListSection.allEpisodes.rawValue)\\n                .tag(ListSection.allEpisodes)\\n            Text(ListSection.downloaded.rawValue)\\n                .tag(ListSection.downloaded)\\n        }.pickerStyle(SegmentedPickerStyle())\\n        .padding(.vertical, 5)\\n        .background(Color.background)\\n    }\\n    // highlight-end\\n\\n}\\n\")), mdx(\"div\", {\n    id: \"Lesson15EpisodeViewHeader0\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614676839/StreamCast/Lesson%2015/xqlufhi9n8l3s6aihjir.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614676839/StreamCast/Lesson%2015/xqlufhi9n8l3s6aihjir.png\"\n  }))))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15EpisodeViewHeader1\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        EpisodeViewHeader()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15EpisodeViewHeader2\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n    // highlight-start\\n    var episode: EpisodeViewModel\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n\\n    // highlight-start\\n    private func downloadEpisode() {\\n\\n    }\\n    // highlight-start\\n\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15EpisodeViewHeader3\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n    var episode: EpisodeViewModel\\n    var downloadEpisode = {}\\n\\n    var body: some View {\\n        // highlight-start\\n        HStack {\\n            Text(episode.date)\\n            Spacer()\\n            Button(action: downloadEpisode, label: {\\n                Image(systemName: \\\"icloud.and.arrow.down.fill\\\")\\n                    .resizable()\\n                    .scaledToFit()\\n                    .frame(width: 20, height: 20)\\n                    .foregroundColor(.border)\\n            }).buttonStyle(BorderlessButtonStyle())\\n        }\\n        // highlight-end\\n    }\\n\\n    private func downloadEpisode() {\\n\\n    }\\n\\n}\\n\")), mdx(\"div\", {\n    id: \"Lesson15EpisodeViewBody0\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614677112/StreamCast/Lesson%2015/qnquikidgihqvbplcfr6.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614677112/StreamCast/Lesson%2015/qnquikidgihqvbplcfr6.png\"\n  }))))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15EpisodeViewBody1\\n// $$liquidcoderFilename-EpisodeViewBody\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewBody: View {\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct EpisodeViewBody_Previews: PreviewProvider {\\n    static var previews: some View {\\n        EpisodeViewBody()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15EpisodeViewBody2\\n// $$liquidcoderFilename-EpisodeViewBody\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewBody: View {\\n\\n    // highlight-start\\n    var episode: EpisodeViewModel\\n    // highlight-end\\n    // highlight-start\\n    var play = {}\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct EpisodeViewBody_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            EpisodeViewBody(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewBody(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15EpisodeViewBody3\\n// $$liquidcoderFilename-EpisodeViewBody\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewBody: View {\\n\\n    var episode: EpisodeViewModel\\n    var play = {}\\n\\n    // highlight-start\\n    private var title: some View {\\n        Button(action: play) {\\n            Text(episode.title)\\n                .font(.headline)\\n                .lineLimit(1)\\n\\n                .foregroundColor(.text)\\n                .fixedSize(horizontal: false, vertical: true)\\n        }\\n    }\\n\\n    // highlight-end\\n\\n    // highlight-start\\n    private var description: some View {\\n        Text(episode.description)\\n            .lineLimit(1)\\n            .font(.subheadline)\\n            .foregroundColor(Color.gray)\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        // highlight-start\\n        VStack(alignment: .leading) {\\n            title\\n            description\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"div\", {\n    id: \"Lesson15EpisodeView0\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614677424/StreamCast/Lesson%2015/e2afg2amem7cgazzmn2z.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614677424/StreamCast/Lesson%2015/e2afg2amem7cgazzmn2z.png\"\n  }))))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15EpisodeView1\\n// $$liquidcoderFilename-EpisodeView\\n\\nimport SwiftUI\\n\\nstruct EpisodeView: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct EpisodeView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            EpisodeView(model: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeView(model: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15EpisodeView2\\n// $$liquidcoderFilename-EpisodeView\\n\\nimport SwiftUI\\n\\nstruct EpisodeView: View {\\n\\n    // highlight-start\\n    var episode: EpisodeViewModel\\n    // highlight-end\\n\\n    // highlight-start\\n    var play = {}\\n    // highlight-end\\n\\n    var body: some View {\\n        // highlight-start\\n        VStack(alignment: .leading, spacing: 5) {\\n            EpisodeViewHeader(episode: episode)\\n            EpisodeViewBody(episode: episode, play: play)\\n        }.padding()\\n        .background(Color.overlay)\\n        .cornerRadius(15)\\n        // highlight-end\\n    }\\n}\\n\\nstruct EpisodeView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            EpisodeView(model: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeView(model: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15UpdatePodcastDetailContent1\\n// $$liquidcoderFilename-PodcastDetailContent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailContent: View {\\n\\n    ...\\n\\n    // highlight-start\\n    private var allEpisodeLink: some View {\\n        NavigationLink(destination: EpisodeListScreen(episodes: podcast.episodes)) {\\n            Label(\\\"See all \\\\(podcast.episodes.count) episodes\\\", systemImage: \\\"square.stack.3d.down.right.fill\\\")\\n        }.padding(.horizontal)\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        GeometryReader { geometry in\\n            ScrollView(showsIndicators: false, content: {\\n                VStack {\\n                    createImage(size: geometry.size.width)\\n\\n                    VStack(alignment: .leading, spacing: 10) {\\n                        PodcastDetailHeaderComponent(podcast: podcast)\\n                        // highlight-start\\n                        allEpisodeLink\\n                        Divider()\\n                        // highlight-end\\n                        description\\n\\n                    }\\n                }\\n            }).navigationBarTitle(\\\"\\\", displayMode: .inline)\\n        }\\n    }\\n\\n    ...\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson15Intro-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey welcome to lesson #15. In the previous one, we started working on the PodcastDetailScreen, and in this one we will continue from where we left off. Let's jump right into it.\")), mdx(\"div\", {\n    id: \"Lesson15UpdateMyLibraryGenreList0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Fixing MyLibraryGenreList Preview\"), mdx(\"p\", null, \"In a couple of lessons back, I told that we will get back to this view fix its preview, let's do that now before we move on.\")), mdx(\"div\", {\n    id: \"Lesson15UpdateMyLibraryGenreList1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Open the MyLibraryGenreList.swift, You can click \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"shift + cmd + o\"), \" to search for the file. Then add the following in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onAppear\"), \" modifier bock.\"), mdx(\"p\", null, \"What we are doing here is fetching genres only there's none saved in memory.\")), mdx(\"div\", {\n    id: \"Lesson15EpisodeListScreen0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"EpisodeList Screen\"), mdx(\"p\", null, \"This will display a list of episodes for a particular podcast. On iPad this will be on the same page as the podcast detail whereas on iPad it will be on its own screen.\")), mdx(\"div\", {\n    id: \"Lesson15EpisodeListScreen1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"EpisodeList Screen\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens\")), \" folder, create another folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"EpisodeList\")), \", and inside it add a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"EpisodeListScreen.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\"))), mdx(\"div\", {\n    id: \"Lesson15EpisodeListScreen2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We will get episodes from the calling view.\"), mdx(\"p\", null, \"Then we create an enum that defines the type of sections we will have in this screen, and as you can see we will give the user the ability to filter out only downloaded episodes.\")), mdx(\"div\", {\n    id: \"Lesson15EpisodeListScreen3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We create the section that goes inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LazyVGrid\"), \" , we create a segmentedPicker that we pass in as the header. We of course still need to create that \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createSegmentedPicker\"), \" function.\"), mdx(\"p\", null, \"In body, we return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ScrollView\"), \" containing a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LazyVGrid\"))), mdx(\"div\", {\n    id: \"Lesson15EpisodeListScreen4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We create the picker, binding the selection to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"listSection\"), \" state property because we will use that type as tags for the 2 segments.\"), mdx(\"p\", null, \"Don't worry about the missing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeView\"), \", we will create it shortly.\")), mdx(\"div\", {\n    id: \"Lesson15EpisodeViewHeader0-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"As you can see the highlighted section on the episode view, this view will be a small part visually, but will contain some of the complex logics in the entire app.\")), mdx(\"div\", {\n    id: \"Lesson15EpisodeViewHeader1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"EpisodeList \\u2192 Components\")), \" folder, create a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"EpisodeViewHeader.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\"))), mdx(\"div\", {\n    id: \"Lesson15EpisodeViewHeader2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We need the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"episode\"), \" view model, so we declare it. That \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"downloadEpisode\"), \" will be filled out in a later lesson.\")), mdx(\"div\", {\n    id: \"Lesson15EpisodeViewHeader3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We put the episode release date and the download button in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HStack\"), \" container separated by a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Spacer\"), \" view so that they are pushed to the extremities of the parent view.\")), mdx(\"div\", {\n    id: \"Lesson15EpisodeViewBody0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"EpisodeViewBody Component\"), mdx(\"p\", null, \"This is the middle component that will be displayed below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeViewHeader\"), \" as you can see on the highlighted picture.\")), mdx(\"div\", {\n    id: \"Lesson15EpisodeViewBody1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"EpisodeList \\u2192 Components\")), \" folder, create a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"EpisodeViewBody.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\"))), mdx(\"div\", {\n    id: \"Lesson15EpisodeViewBody2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We need the episode view model here as well. We also declare a closure that will run when the user plays this particular episode.\")), mdx(\"div\", {\n    id: \"Lesson15EpisodeViewBody3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We create the title and description that we put in a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" inside body. Notice that we surround the title with a button, and for its action we pass the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"play\"), \" closure.\")), mdx(\"div\", {\n    id: \"Lesson15EpisodeView0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"EpisodeView Component\"), mdx(\"p\", null, \"This is the component that will be composed of the 2 parts we\\u2019ve just created, and later we will create a footer for it.\")), mdx(\"div\", {\n    id: \"Lesson15EpisodeView1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"EpisodeList \\u2192 Components\")), \" folder, create a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"EpisodeView.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\"))), mdx(\"div\", {\n    id: \"Lesson15EpisodeView2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We  declare the 2 properties that we declared in the other components.\"), mdx(\"p\", null, \"Then in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \", we stack the 2 component inside a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" container.\")), mdx(\"div\", {\n    id: \"Lesson15EpisodeView21-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now if you build the project \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"(CMD + B),\"), \" the error should leave you alone.\")), mdx(\"div\", {\n    id: \"Lesson15UpdatePodcastDetailContent1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Let\\u2019s update the PodcastDetailContent with that code.\"), mdx(\"p\", null, \"Here we are creating a link that will be used to navigate to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeListScreen\"))), mdx(\"div\", {\n    id: \"Lesson15Outro-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And this brings another lesson to a close\\u2026 See you guys in lesson #16 where we will start working on the download feature which will be fun. Happy coding!!!\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#fixing-mylibrarygenrelist-preview","title":"Fixing MyLibraryGenreList Preview"},{"url":"#episodelist-screen","title":"EpisodeList Screen"},{"url":"#episodelist-screen-1","title":"EpisodeList Screen"},{"url":"#episodeviewbody-component","title":"EpisodeViewBody Component"},{"url":"#episodeview-component","title":"EpisodeView Component"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey welcome to lesson #15. In the previous one, we started working on the PodcastDetailScreen, and in this one we will continue from where we left off. Let's right into it.\"), mdx(\"h2\", null, \"Fixing MyLibraryGenreList Preview\"), mdx(\"p\", null, \"In a couple of lessons back, I told that we will get back to this view fix its preview, let's do that now before we move on.\"), mdx(\"p\", null, \"Open the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"MyLibraryGenreList.swift,\"), \" You can click \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"shift + cmd + o\"), \" to search for the file. Then add the following in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onAppear\"), \" modifier bock.\"), mdx(\"p\", null, \"What we are doing here is fetching genres only there's none saved in memory.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15UpdateMyLibraryGenreList1\\n// $$liquidcoderFilename-MyLibraryGenreList\\n\\nimport SwiftUI\\n\\nstruct MyLibraryGenreList: View {\\n\\n    @Binding var selected: String\\n\\n    @EnvironmentObject private var store: Store\\n    @State private var genres: [GenreViewModel] = []\\n\\n    var body: some View {\\n        ScrollView(.horizontal, showsIndicators: false, content: {\\n            ScrollViewReader { reader in\\n                createScrollViewContent(reader: reader)\\n            }\\n        }).padding(.vertical, 10)\\n        .background(Color.background)\\n        .onAppear {\\n            // highlight-start\\n            if store.coredataState.savedGenres.isEmpty {\\n                store.dispatch(.coredata(.fetchGenres))\\n            } else {\\n                genres = store.coredataState.savedGenres\\n            }\\n            // highlight-end\\n        }.onChange(of: store.coredataState.savedGenres, perform: { value in\\n            ...\\n        })\\n    }\\n\\n    fileprivate func createScrollViewContent(reader: ScrollViewProxy) -> some View {\\n        ...\\n    }\\n\\n    fileprivate func createButton(_ index: Int, reader: ScrollViewProxy) -> some View {\\n        ...\\n    }\\n}\\n\")), mdx(\"h2\", null, \"EpisodeList Screen\"), mdx(\"p\", null, \"This will display a list of episodes for a particular podcast. On iPad this will be on the same page as the podcast detail whereas on iPad it will be on its own screen.\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens\")), \" folder, create another folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"EpisodeList\")), \", and inside it add a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"EpisodeListScreen.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15EpisodeListScreen2\\n// $$liquidcoderFilename-EpisodeListScreen\\n\\nimport SwiftUI\\n\\nstruct EpisodeListScreen: View {\\n\\n    // highlight-start\\n    var episodes: [EpisodeViewModel]\\n    // highlight-end\\n\\n    // highlight-start\\n    enum ListSection: String, CaseIterable {\\n        case downloaded = \\\"Downloaded\\\"\\n        case allEpisodes = \\\"All episodes\\\"\\n    }\\n\\n    @State private var listSection = ListSection.allEpisodes\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct EpisodeListScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        // highlight-start\\n        Group {\\n            EpisodeListScreen(episodes: EpisodeViewModel.listPlaceholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeListScreen(episodes: EpisodeViewModel.listPlaceholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"We will get episodes from the calling view.\"), mdx(\"p\", null, \"Then we create an enum that define the type of sections we will have in this screen, and as you can see we will give the user the ability to filter out only downloaded episodes.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14EpisodeListScreen3\\n// $$liquidcoderFilename-EpisodeListScreen\\n\\nimport SwiftUI\\n\\nstruct EpisodeListScreen: View {\\n\\n        ...\\n\\n        // highlight-start\\n\\n        private var gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n\\n        private var section: some View {\\n        Section(header: createSegmentedPicker()) {\\n            ForEach(episodes) { episode in\\n                EpisodeView(episode: episode, play: {})\\n            }\\n        }\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        // highlight-start\\n                ScrollView {\\n            LazyVGrid(columns: gridItems,\\n                      alignment: .leading,\\n                      spacing: 20,\\n                      content: { section }\\n            ).padding()\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"We create the section that goes inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LazyVGrid\"), \" , we create a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"segmentedPicker\"), \" that we pass in as the header. We of course still need to create that \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createSegmentedPicker\"), \" function.\"), mdx(\"p\", null, \"In body, we return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ScrollView\"), \" containing a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LazyVGrid\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14EpisodeListScreen4\\n// $$liquidcoderFilename-EpisodeListScreen\\n\\nimport SwiftUI\\n\\nstruct EpisodeListScreen: View {\\n\\n        ...\\n\\n    private var section: some View {\\n        Section(header: createSegmentedPicker()) {\\n            ForEach(episodes) { episode in\\n                EpisodeView(episode: episode, play: {})\\n            }\\n        }\\n    }\\n\\n    var body: some View {\\n\\n                ScrollView {\\n            LazyVGrid(columns: GridItems.shared,\\n                      alignment: .leading,\\n                      spacing: 20,\\n                      content: { section }\\n            ).padding()\\n        }\\n    }\\n\\n// highlight-start\\n    func createSegmentedPicker() -> some View {\\n        Picker(\\\"\\\", selection: $listSection) {\\n            Text(ListSection.allEpisodes.rawValue)\\n                .tag(ListSection.allEpisodes)\\n            Text(ListSection.downloaded.rawValue)\\n                .tag(ListSection.downloaded)\\n        }.pickerStyle(SegmentedPickerStyle())\\n        .padding(.vertical, 5)\\n        .background(Color.background)\\n    }\\n    // highlight-end\\n\\n}\\n\")), mdx(\"p\", null, \"We create the picker, binding the selection to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"listSection\"), \" state property because we will use that type as tags for the 2 segments.\"), mdx(\"p\", null, \"Don\\u2019t worry about the missing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeView\"), \", we will create it shortly.\"), mdx(\"h2\", null, \"EpisodeViewHeader\"), mdx(\"p\", null, \"As you can see the highlighted section on the episode view, this view will be a small part visually, but will contain some of the complex logics in the entire app.\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"EpisodeList \\u2192 Components\")), \" folder, create a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"EpisodeViewHeader.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14EpisodeViewHeader2\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n    // highlight-start\\n    var episode: EpisodeViewModel\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n\\n        // highlight-start\\n    private func downloadEpisode() {\\n\\n    }\\n    // highlight-start\\n\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"We need the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"episode\"), \" view model, so we declare it. That \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"downloadEpisode\"), \" will be filled out in a later lesson.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14EpisodeViewHeader3\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n    var episode: EpisodeViewModel\\n    var downloadEpisode = {}\\n\\n    var body: some View {\\n        // highlight-start\\n        HStack {\\n            Text(episode.date)\\n            Spacer()\\n            Button(action: downloadEpisode, label: {\\n                Image(systemName: \\\"icloud.and.arrow.down.fill\\\")\\n                    .resizable()\\n                    .scaledToFit()\\n                    .frame(width: 20, height: 20)\\n                    .foregroundColor(.border)\\n            }).buttonStyle(BorderlessButtonStyle())\\n        }\\n        // highlight-end\\n    }\\n\\n    private func downloadEpisode() {\\n\\n    }\\n\\n}\\n\")), mdx(\"p\", null, \"We put the episode release date and the download button in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HStack\"), \" container separated by a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Spacer\"), \" view so that they are pushed to the extremities of the parent view.\"), mdx(\"h2\", null, \"EpisodeViewBody Component\"), mdx(\"p\", null, \"This is the middle component that will be displayed below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeViewHeader\"), \" as you can see on the highlighted picture.\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"EpisodeList \\u2192 Components\")), \" folder, create a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"EpisodeViewBody.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14EpisodeViewBody2\\n// $$liquidcoderFilename-EpisodeViewBody\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewBody: View {\\n\\n    // highlight-start\\n    var episode: EpisodeViewModel\\n    // highlight-end\\n    // highlight-start\\n    var play = {}\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct EpisodeViewBody_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            EpisodeViewBody(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewBody(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"We need the episode view model here as well. We also declare a closure that will run when the user plays this particular episode.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14EpisodeViewBody3\\n// $$liquidcoderFilename-EpisodeViewBody\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewBody: View {\\n\\n    var episode: EpisodeViewModel\\n    var play = {}\\n\\n    // highlight-start\\n    private var title: some View {\\n        Button(action: play) {\\n            Text(episode.title)\\n                .font(.headline)\\n                .lineLimit(1)\\n\\n                .foregroundColor(.text)\\n                .fixedSize(horizontal: false, vertical: true)\\n        }\\n    }\\n\\n    // highlight-end\\n\\n    // highlight-start\\n    private var description: some View {\\n        Text(episode.description)\\n            .lineLimit(1)\\n            .font(.subheadline)\\n            .foregroundColor(Color.gray)\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        // highlight-start\\n        VStack(alignment: .leading) {\\n            title\\n            description\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"We create the title and description that we put in a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" inside body. Notice that we surround the title with a button, and for its action we pass the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"play\"), \" closure.\"), mdx(\"h2\", null, \"EpisodeView Component\"), mdx(\"p\", null, \"This is the component that will be composed of the 2 parts we\\u2019ve just created, and later we will create a footer for it.\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"EpisodeList \\u2192 Components\")), \" folder, create a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"EpisodeView.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14EpisodeView1\\n// $$liquidcoderFilename-EpisodeView\\n\\nimport SwiftUI\\n\\nstruct EpisodeView: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct EpisodeView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            EpisodeView(model: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeView(model: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson14EpisodeView2\\n// $$liquidcoderFilename-EpisodeView\\n\\nimport SwiftUI\\n\\nstruct EpisodeView: View {\\n\\n    // highlight-start\\n    var episode: EpisodeViewModel\\n    // highlight-end\\n\\n    // highlight-start\\n    var play = {}\\n    // highlight-end\\n\\n    var body: some View {\\n        // highlight-start\\n        VStack(alignment: .leading, spacing: 5) {\\n            EpisodeViewHeader(episode: episode)\\n            EpisodeViewBody(episode: episode, play: play)\\n        }.padding()\\n        .background(Color.overlay)\\n        .cornerRadius(15)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"We  declare the 2 properties that we declared in the other components.\"), mdx(\"p\", null, \"Then in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \", we stack the 2 component inside a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" container.\"), mdx(\"p\", null, \"Now if you build the project \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"(CMD + B),\"), \" the error should leave you alone.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15UpdatePodcastDetailContent1\\n// $$liquidcoderFilename-PodcastDetailContent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailContent: View {\\n\\n        ...\\n    // highlight-start\\n    private var allEpisodeLink: some View {\\n        NavigationLink(destination: EpisodeListScreen(episodes: podcast.episodes)) {\\n            Label(\\\"See all \\\\(podcast.episodes.count) episodes\\\", systemImage: \\\"square.stack.3d.down.right.fill\\\")\\n        }.padding(.horizontal)\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        GeometryReader { geometry in\\n            ScrollView(showsIndicators: false, content: {\\n                VStack {\\n                    createImage(size: geometry.size.width)\\n\\n                    VStack(alignment: .leading, spacing: 10) {\\n                        PodcastDetailHeaderComponent(podcast: podcast)\\n                        // highlight-start\\n                        allEpisodeLink\\n                        Divider()\\n                        // highlight-end\\n                        description\\n\\n                    }\\n                }\\n            }).navigationBarTitle(\\\"\\\", displayMode: .inline)\\n        }\\n    }\\n\\n        ...\\n}\\n\")), mdx(\"p\", null, \"Let\\u2019s update the PodcastDetailContent with that code.\"), mdx(\"p\", null, \"Here we are creating a link that will be used to navigate to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeListScreen\")), mdx(\"p\", null, \"And this brings another lesson to a close\\u2026 See you guys in lesson #16 where we will start working on the download feature which will be fun. Happy coding!!!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#fixing-mylibrarygenrelist-preview","title":"Fixing MyLibraryGenreList Preview"},{"url":"#episodelist-screen","title":"EpisodeList Screen"},{"url":"#episodeviewheader","title":"EpisodeViewHeader"},{"url":"#episodeviewbody-component","title":"EpisodeViewBody Component"},{"url":"#episodeview-component","title":"EpisodeView Component"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-03-05T09:00:15.840Z","isNewLayout":true,"previews":null,"slug":"lesson-16-downloader-service","title":"Lesson #16: Downloader Service","updatedDate":"2021-03-05T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZeCQfXZxXwCONaDqgFDNGszGj7LVuVjorSV","type":"zip","id":"e9246f6f-2155-5a15-b2f7-fa09395d4a6c","name":"Source code"}],"allChapters":null,"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson16DownloaderServiceProtocol1\\n// $$liquidcoderFilename-DownloaderServiceProtocol\\n\\n// highlight-start\\nimport SwiftUI\\n// highlight-end\\n\\n// highlight-start\\nprotocol DownloaderServiceProtocol: URLSessionDownloadDelegate {\\n\\n    var progress: ((CGFloat) -> Void) { get set }\\n    var didFinish: ((Data?) -> Void) { get set }\\n    func download(from url: URL)\\n    func cancel()\\n}\\n// highlight-end\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson16DownloaderService1\\n// $$liquidcoderFilename-DownloaderService\\n\\nimport Foundation\\n// highlight-start\\nimport SwiftUI\\n// highlight-end\\n\\nclass DownloaderService: NSObject, DownloaderServiceProtocol {\\n\\n    // highlight-start\\n    var progress: ((CGFloat) -> Void) = { _ in }\\n\\n    var didFinish: ((URL) -> Void) = { _ in }\\n    // highlight-end\\n\\n    // highlight-start\\n    func download(from url: URL) {\\n\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    func cancel() {\\n\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {\\n\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson16DownloaderService2\\n// $$liquidcoderFilename-DownloaderService\\n\\nimport Foundation\\nimport SwiftUI\\n\\nclass DownloaderService: NSObject, DownloaderServiceProtocol {\\n\\n    ...\\n\\n    // highlight-start\\n    private var session: URLSession {\\n        let config = URLSessionConfiguration.default\\n        config.waitsForConnectivity = true\\n        return URLSession(configuration: config, delegate: self, delegateQueue: nil)\\n    }\\n    // highlight-end\\n\\n    func download(from url: URL) {\\n\\n    }\\n\\n    func cancel() {\\n\\n    }\\n\\n    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {\\n\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson16DownloaderService3\\n// $$liquidcoderFilename-DownloaderService\\n\\nimport Foundation\\nimport SwiftUI\\n\\nclass DownloaderService: NSObject, DownloaderServiceProtocol {\\n\\n    ...\\n\\n    private var session: URLSession {\\n        let config = URLSessionConfiguration.default\\n        config.waitsForConnectivity = true\\n        return URLSession(configuration: config, delegate: self, delegateQueue: nil)\\n    }\\n\\n    func download(from url: URL) {\\n\\n        // highlight-start\\n        let task = session.downloadTask(with: url)\\n        task.resume()\\n        // highlight-end\\n\\n    }\\n\\n    // highlight-start\\n    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {\\n        Log.info(\\\"Bytes written: \\\\(totalBytesWritten), Total bytes: \\\\(totalBytesExpectedToWrite)\\\")\\n    }\\n    // highlight-end\\n\\n    func cancel() {\\n\\n    }\\n\\n    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {\\n\\n    }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson16DownloaderService3Testing\\n// $$liquidcoderFilename-ContentView\\n\\nimport SwiftUI\\nimport CoreData\\n\\nstruct ContentView: View {\\n\\n    @FetchRequest(\\n        sortDescriptors:\\n            [NSSortDescriptor(keyPath: \\\\GenreManagedObject.id, ascending: true)]\\n    ) private var genres: FetchedResults<GenreManagedObject>\\n\\n    var body: some View {\\n        if genres.isEmpty {\\n            GenreSelectionScreen()\\n        } else {\\n            NavigationView {\\n                HomeScreen().navigationTitle(\\\"Home\\\")\\n\\n            // highlight-start\\n            }.onAppear(perform: {\\n                DownloaderService().download(from: URL(string: \\\"https://elt.oup.com/elt/students/headway/downloads/headway_beginner_students_book_unit_01.zip?cc=gb&selLanguage=en\\\")! )\\n            })\\n            // highlight-end\\n        }\\n    }\\n}\\n\")), mdx(\"div\", {\n    id: \"Lesson16DownloaderService3LogFix1\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614850112/StreamCast/Lesson%2016/qgd8ck47ttuoolnkjokw.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614850112/StreamCast/Lesson%2016/qgd8ck47ttuoolnkjokw.png\"\n  }))))), mdx(\"div\", {\n    id: \"Lesson16DownloaderService3LogFix2\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614850111/StreamCast/Lesson%2016/uyt0dzg3chpuwxj2atvq.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614850111/StreamCast/Lesson%2016/uyt0dzg3chpuwxj2atvq.png\"\n  }))))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson16DownloaderService3LogFix3\\n// $$liquidcoderFilename-Log\\n\\nstruct Log {\\n\\n        ...\\n\\n    // highlight-start\\n    static func error(_ object: Any..., filename: String = #file, line: Int = #line, column: Int = #column, funcName: String = #function) {\\n        message(forEvent: .error, object,filename: filename,line: line,funcName: funcName)\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    static func info(_ object: Any..., filename: String = #file, line: Int = #line, column: Int = #column, funcName: String = #function) {\\n\\n        message(forEvent: .info, object,filename: filename,line: line,funcName: funcName)\\n\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    static func warning(_ object: Any..., filename: String = #file,line: Int = #line, column: Int = #column, funcName: String = #function) {\\n\\n        message(forEvent: .warning, object,filename: filename,line: line,funcName: funcName)\\n\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    static func debug(_ object: Any..., filename: String = #file, line: Int = #line,column: Int = #column, funcName: String = #function) {\\n\\n        message(forEvent: .debug, object,filename: filename,line: line,funcName: funcName)\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    static func verbose(_ object: Any..., filename: String = #file, line: Int = #line, column: Int = #column, funcName: String = #function) {\\n\\n        message(forEvent: .verbose, object,filename: filename,line: line,funcName: funcName)\\n\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    static func severe(_ object: Any..., filename: String = #file, line: Int = #line, column: Int = #column, funcName: String = #function) {\\n\\n        message(forEvent: .severe, object, filename: filename,line: line,funcName: funcName)\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"div\", {\n    id: \"Lesson16DownloaderService3LogFix4\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614850112/StreamCast/Lesson%2016/ybkqk9iniico7pgppble.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614850112/StreamCast/Lesson%2016/ybkqk9iniico7pgppble.png\"\n  }))))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson16DownloaderService4\\n// $$liquidcoderFilename-DownloaderService\\n\\nimport Foundation\\nimport SwiftUI\\n\\nclass DownloaderService: NSObject, DownloaderServiceProtocol {\\n\\n    ...\\n\\n    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {\\n\\n        // highlight-start\\n        if totalBytesExpectedToWrite < 0 {\\n            progress(0)\\n            return\\n        }\\n        // highlight-end\\n\\n        // highlight-start\\n        let percentage = CGFloat(totalBytesWritten) / CGFloat(totalBytesExpectedToWrite)\\n        progress(percentage)\\n        // highlight-end\\n\\n    }\\n\\n    func cancel() {\\n\\n    }\\n\\n    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {\\n\\n    }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson16DownloaderService5\\n// $$liquidcoderFilename-DownloaderService\\n\\nimport Foundation\\nimport SwiftUI\\n\\nclass DownloaderService: NSObject, DownloaderServiceProtocol {\\n\\n    ... \\n\\n    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {\\n\\n        if totalBytesExpectedToWrite < 0 {\\n            progress(0)\\n            return\\n        }\\n\\n        let percentage = CGFloat(totalBytesWritten) / CGFloat(totalBytesExpectedToWrite)\\n        progress(percentage)\\n\\n    }\\n\\n    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {\\n        // highlight-start\\n       didFinish(location)\\n        // highlight-end\\n    }\\n\\n    func cancel() {\\n        // highlight-start\\n        session.invalidateAndCancel()\\n        // highlight-end\\n    }\\n\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson16Intro-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey guys, welcome to lesson #16. In the previous lesson, we continued working on the podcast detail screen, and in this one, we are going to move away from the UI stuff for a little bit to implement the download feature.\"), mdx(\"p\", null, \"The entire feature will span 3 lessons, and some of the concepts we will utilise may or will totally be new to you, so in order to understand better, don\\u2019t just skim through the lessons\\u2026 Be patient, learn slowly and read the documentation I have provided. Let\\u2019s go\")), mdx(\"div\", {\n    id: \"Lesson16DownloaderServiceProtocol1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"DownloaderService Protocol\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Networking\")), \" folder, add a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"DownloaderServiceProtocol.\")), mdx(\"p\", null, \"As always, we first create the protocol, and as you can see we have 2 methods and 2 closures.\"), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"progress\"), \" will be called on each download progress update.\"), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"didFinish\"), \" closure will only be called when the download process completes\"), mdx(\"p\", null, \"And the 2 methods will be called to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"download\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cancel\"))), mdx(\"div\", {\n    id: \"Lesson16DownloaderService1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"DownloaderService\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Networking\")), \" folder, add a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"DownloaderService.\")), mdx(\"p\", null, \"The class must extends the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NSObject\"), \" class before it conforms to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DownloaderServiceProtocol\")), mdx(\"p\", null, \"Not only do we need to implement the 4 required properties from the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DownloaderServiceProtocol\"), \", we are also  required to implemented the required method from the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"URLSessionDownloadDelegate\"))), mdx(\"div\", {\n    id: \"Lesson16DownloaderService2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We first create the session just like we did in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiService\"), \" class, but this time we don't need to setup the cache and the other stuff, what we need is the default configuration and set \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"waitsForConnectivity\"), \" to true.\")), mdx(\"div\", {\n    id: \"Lesson16DownloaderService3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"download\"), \" method, we create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"downloadTask\"), \" using the provided url that we then resume to start the download process.\"), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"urlSession (didWriteData)\"), \" method, we simply print out the progress to the console for now. This method is now required, but we need it to update the progress.\")), mdx(\"div\", {\n    id: \"Lesson16DownloaderService3Testing-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Testing the downloader service\"), mdx(\"p\", null, \"To test this, run the following code in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"ContentView.swift\"), \" file, and run the project.\"), mdx(\"p\", null, \"So here, we are creating a new \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DownloaderService\"), \" instance that we use to call the download method passing in a random url to download some sort of zip file\")), mdx(\"div\", {\n    id: \"Lesson16DownloaderService3LogFix1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"As you can see the number of bytes downloaded and the total are being printed out as the download progresses.\")), mdx(\"div\", {\n    id: \"Lesson16DownloaderService3LogFix2-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Fixing the Log utility\"), mdx(\"p\", null, \"There is a problem with the Log here, it does not reflect the correct file, method and line number where the event has occurred, let\\u2019s fix that.\")), mdx(\"div\", {\n    id: \"Lesson16DownloaderService3LogFix3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"All we have to do is to change the signatures of all the methods that are being called, and calling the reused \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"message\"), \" function passing the full list of parameters.\")), mdx(\"div\", {\n    id: \"Lesson16DownloaderService3LogFix4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now you can run the app, and you should see that  the correct file, method as well as the line number are being printed out to the console.\")), mdx(\"div\", {\n    id: \"Lesson16DownloaderService4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"urlSession(didWriteData)\"), \" method, we first check whether the total bytes to be written is greater than 0 meaning we have something valid to download, if we don't we call the progress passing in 0, and stop the process, otherwise we proceed.\"), mdx(\"p\", null, \"We then calculate the  progress in percentages and call the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"progress\"), \" closure passing in the calculated values\")), mdx(\"div\", {\n    id: \"Lesson16DownloaderService5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"urlSession(didFinishDownloadingTo)\"), \", we first read the data from the temporary location, then call the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"didFinish\"), \" closure passing in the data.\"), mdx(\"p\", null, \"In cancel, we call the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"invalidateAndCancel\"), \" on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"session\"), \" object to stop the current download operation.\")), mdx(\"div\", {\n    id: \"Lesson16Outro-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And this is the end of this short lesson, I wanted you to focus on how to implement the download so that it would easier to understand the next 2 or 3 lessons in which we will work on Operations, saving Episodes in Coredata after being downloaded. See you then and Happy coding!!\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#downloaderservice-protocol","title":"DownloaderService Protocol"},{"url":"#downloaderservice","title":"DownloaderService"},{"url":"#testing-the-downloader-service","title":"Testing the downloader service"},{"url":"#fixing-the-log-utility","title":"Fixing the Log utility"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey guys, welcome to lesson #16. In the previous lesson, we continued working on the podcast detail screen, and in this one, we are going to move away from the UI stuff for a little bit to implement the download feature.\"), mdx(\"p\", null, \"The entire feature will span 3 lessons, and some of the concepts we will utilise may or will totally be new to you, so in order to understand better, don\\u2019t just skim through the lessons\\u2026 Be patient, learn slowly and read the documentation I have provided. Let\\u2019s go\"), mdx(\"h2\", null, \"DownloaderService Protocol\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Networking\"), \" folder, add a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"DownloaderServiceProtocol\"), \".\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15DownloaderServiceProtocol1\\n// $$liquidcoderFilename-DownloaderServiceProtocol\\n\\n// highlight-start\\nimport SwiftUI\\n// highlight-end\\n\\n// highlight-start\\nprotocol DownloaderServiceProtocol: URLSessionDownloadDelegate {\\n\\n    var progress: ((CGFloat) -> Void) { get set }\\n    var didFinish: ((Data?) -> Void) { get set }\\n    func download(from url: URL)\\n    func cancel()\\n}\\n// highlight-end\\n\")), mdx(\"p\", null, \"As always, we first create the protocol, and as you can see we have 2 methods and 2 closures.\"), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"progress\"), \" will be called on each download progress update.\"), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"didFinish\"), \" closure will only be called when the download process completes\"), mdx(\"p\", null, \"And the 2 methods will be called to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"download\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cancel\")), mdx(\"h2\", null, \"DownloaderService\"), mdx(\"p\", null, \"In the Networking folder, add a swift file named DownloaderService.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15DownloaderService1\\n// $$liquidcoderFilename-DownloaderService\\n\\nimport Foundation\\nimport SwiftUI\\n\\nclass DownloaderService: NSObject, DownloaderServiceProtocol {\\n\\n    // highlight-start\\n        var progress: ((CGFloat) -> Void) = { _ in }\\n\\n    var didFinish: ((URL) -> Void) = { _ in }\\n    // highlight-end\\n\\n    // highlight-start\\n    func download(from url: URL) {\\n\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    func cancel() {\\n\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {\\n\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"p\", null, \"The class must extends the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NSObject\"), \" class before it conforms to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DownloaderServiceProtocol\")), mdx(\"p\", null, \"Not only do we need to implement the 4 required properties from the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DownloaderServiceProtocol\"), \", we are also\\xA0 required to implemented the required method from the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"URLSessionDownloadDelegate\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15DownloaderService2\\n// $$liquidcoderFilename-DownloaderService\\n\\nimport Foundation\\nimport SwiftUI\\n\\nclass DownloaderService: NSObject, DownloaderServiceProtocol {\\n\\n    // highlight-start\\n    private var session: URLSession {\\n        let config = URLSessionConfiguration.default\\n        config.waitsForConnectivity = true\\n        return URLSession(configuration: config, delegate: self, delegateQueue: nil)\\n    }\\n    // highlight-end\\n\\n    func download(from url: URL) {\\n\\n    }\\n\\n    func cancel() {\\n\\n    }\\n\\n    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {\\n\\n    }\\n}\\n\")), mdx(\"p\", null, \"We first create the session just like we did in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ApiService\"), \" class, but this time we don't need to setup the cache and the other stuff, what we need is the default configuration and set \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"waitsForConnectivity\"), \" to true.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15DownloaderService3\\n// $$liquidcoderFilename-DownloaderService\\n\\nimport Foundation\\nimport SwiftUI\\n\\nclass DownloaderService: NSObject, DownloaderServiceProtocol {\\n\\n        ...\\n\\n    private var session: URLSession {\\n        let config = URLSessionConfiguration.default\\n        config.waitsForConnectivity = true\\n        return URLSession(configuration: config, delegate: self, delegateQueue: nil)\\n    }\\n\\n    func download(from url: URL) {\\n\\n        // highlight-start\\n        let task = session.downloadTask(with: url)\\n        task.resume()\\n        // highlight-end\\n\\n    }\\n\\n    // highlight-start\\n    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {\\n        Log.info(\\\"Bytes written: \\\\(totalBytesWritten), Total bytes: \\\\(totalBytesExpectedToWrite)\\\")\\n    }\\n    // highlight-end\\n\\n    func cancel() {\\n\\n    }\\n\\n    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {\\n\\n    }\\n\\n}\\n\")), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"download\"), \" method, we create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"downloadTask\"), \" using the provided url that we then resume to start the download process.\"), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"urlSession (didWriteData)\"), \" method, we simply print out the progress to the console for now. This method is now required, but we need it to update the progress.\"), mdx(\"h2\", null, \"Testing the downloader service\"), mdx(\"p\", null, \"To test this, run the following code in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"ContentView.swift\"), \" file, and run the project.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15DownloaderService3Testing\\n// $$liquidcoderFilename-ContentView\\n\\nimport SwiftUI\\nimport CoreData\\n\\nstruct ContentView: View {\\n\\n    @FetchRequest(\\n        sortDescriptors:\\n            [NSSortDescriptor(keyPath: \\\\GenreManagedObject.id, ascending: true)]\\n    ) private var genres: FetchedResults<GenreManagedObject>\\n\\n    var body: some View {\\n        if genres.isEmpty {\\n            GenreSelectionScreen()\\n        } else {\\n            NavigationView {\\n                HomeScreen().navigationTitle(\\\"Home\\\")\\n\\n            // highlight-start\\n            }.onAppear(perform: {\\n                DownloaderService().download(from: URL(string: \\\"https://elt.oup.com/elt/students/headway/downloads/headway_beginner_students_book_unit_01.zip?cc=gb&selLanguage=en\\\")! )\\n            })\\n            // highlight-end\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"So here, we are creating a new \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DownloaderService\"), \" instance that we use to call the download method passing in a random url to download some sort of zip file\"), mdx(\"p\", null, \"As you can see the number of bytes downloaded and the total are being printed out as the download progresses.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614850112/StreamCast/Lesson%2016/qgd8ck47ttuoolnkjokw.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614850112/StreamCast/Lesson%2016/qgd8ck47ttuoolnkjokw.png\"\n  }))), mdx(\"h2\", null, \"Fixing the Log utility\"), mdx(\"p\", null, \"There is a problem with the Log here, it does not reflect the correct file, method and line number where the event has occurred, let\\u2019s fix that.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614850111/StreamCast/Lesson%2016/uyt0dzg3chpuwxj2atvq.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614850111/StreamCast/Lesson%2016/uyt0dzg3chpuwxj2atvq.png\"\n  }))), mdx(\"p\", null, \"All we have to do is to change the signatures of all the methods that are being called, and calling the reused \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"message\"), \" function passing the full list of parameters.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15DownloaderService3LogFix2\\n// $$liquidcoderFilename-Log\\n\\nstruct Log {\\n\\n        ...\\n\\n    // highlight-start\\n    static func error(_ object: Any..., filename: String = #file, line: Int = #line, column: Int = #column, funcName: String = #function) {\\n        message(forEvent: .error, object,filename: filename,line: line,funcName: funcName)\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    static func info(_ object: Any..., filename: String = #file, line: Int = #line, column: Int = #column, funcName: String = #function) {\\n\\n        message(forEvent: .info, object,filename: filename,line: line,funcName: funcName)\\n\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    static func warning(_ object: Any..., filename: String = #file,line: Int = #line, column: Int = #column, funcName: String = #function) {\\n\\n        message(forEvent: .warning, object,filename: filename,line: line,funcName: funcName)\\n\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    static func debug(_ object: Any..., filename: String = #file, line: Int = #line,column: Int = #column, funcName: String = #function) {\\n\\n        message(forEvent: .debug, object,filename: filename,line: line,funcName: funcName)\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    static func verbose(_ object: Any..., filename: String = #file, line: Int = #line, column: Int = #column, funcName: String = #function) {\\n\\n        message(forEvent: .verbose, object,filename: filename,line: line,funcName: funcName)\\n\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    static func severe(_ object: Any..., filename: String = #file, line: Int = #line, column: Int = #column, funcName: String = #function) {\\n\\n        message(forEvent: .severe, object, filename: filename,line: line,funcName: funcName)\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"p\", null, \"Now you can run the app, and you should see that the correct file, method as well as the line number are being printed out to the console.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614850112/StreamCast/Lesson%2016/ybkqk9iniico7pgppble.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614850112/StreamCast/Lesson%2016/ybkqk9iniico7pgppble.png\"\n  }))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15DownloaderService4\\n// $$liquidcoderFilename-DownloaderService\\n\\nimport Foundation\\nimport SwiftUI\\n\\nclass DownloaderService: NSObject, DownloaderServiceProtocol {\\n\\n        ...\\n\\n    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {\\n\\n        // highlight-start\\n        if totalBytesExpectedToWrite < 0 {\\n            progress(0)\\n            return\\n        }\\n        // highlight-end\\n\\n        // highlight-start\\n        let percentage = CGFloat(totalBytesWritten) / CGFloat(totalBytesExpectedToWrite)\\n        progress(percentage)\\n        // highlight-end\\n\\n    }\\n\\n    func cancel() {\\n\\n    }\\n\\n    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {\\n\\n    }\\n\\n}\\n\")), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"urlSession(didWriteData)\"), \" method, we first check whether the total bytes to be written is greater than 0 meaning we have something valid to download, if we don't we call the progress passing in 0, and stop the process, otherwise we proceed.\"), mdx(\"p\", null, \"We then calculate the\\xA0 progress in percentages and call the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"progress\"), \" closure passing in the calculated values\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson15DownloaderService5\\n// $$liquidcoderFilename-DownloaderService\\n\\nimport Foundation\\nimport SwiftUI\\n\\nclass DownloaderService: NSObject, DownloaderServiceProtocol {\\n\\n        ...    \\n\\n    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {\\n        // highlight-start\\n       do {\\n            let data = try Data(contentsOf: location)\\n            didFinish(data)\\n        } catch let err {\\n            didFinish(nil)\\n            Log.error(err)\\n        }\\n        // highlight-end\\n    }\\n\\n    func cancel() {\\n        // highlight-start\\n        session.invalidateAndCancel()\\n        // highlight-end\\n    }\\n\\n}\\n\")), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"urlSession(didFinishDownloadingTo)\"), \", we first read the data from the temporary location, then call the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"didFinish\"), \" closure passing in the data.\"), mdx(\"p\", null, \"In cancel, we call the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"invalidateAndCancel\"), \" on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"session\"), \" object to stop the current download operation.\"), mdx(\"p\", null, \"And this is the end of this short lesson, I wanted you to focus on how to implement the download so that it would easier to understand the next 2 or 3 lessons in which we will work on Operations, saving Episodes in \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Coredata\"), \" after being downloaded. See you then and Happy coding!!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#downloaderservice-protocol","title":"DownloaderService Protocol"},{"url":"#downloaderservice","title":"DownloaderService"},{"url":"#testing-the-downloader-service","title":"Testing the downloader service"},{"url":"#fixing-the-log-utility","title":"Fixing the Log utility"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-03-08T08:58:11.040Z","isNewLayout":true,"previews":null,"slug":"lesson-17-download-operation","title":"Lesson #17: Download Operation","updatedDate":"2021-03-08T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZSP9fXZ5RXCLAcvwL0XbOlnc79Vzf0igNLX","type":"zip","id":"d3004a92-0a8c-56cf-9dec-a3628f9afcce","name":"Source code"},{"url":"https://developer.apple.com/documentation/foundation/operation","type":"link","id":"8445a114-d5cb-567e-839b-810396fe8dae","name":"Docs on operation"},{"url":"https://withintent.com/blog/basics-of-operations-and-operation-queues-in-ios/","type":"link","id":"dd37bbd0-c9b7-513a-bfe8-6ee022ade24f","name":"Basics of operations and operation queues"}],"allChapters":null,"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson17BaseOperation1\\n// $$liquidcoderFilename-BaseOperation\\n\\nimport Foundation\\n\\nclass BaseOperation: Operation {\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson17BaseOperation2\\n// $$liquidcoderFilename-BaseOperation\\n\\n// highlight-start\\nimport SwiftUI\\n// highlight-end\\n\\nclass BaseOperation: Operation {\\n\\n    // highlight-start\\n    enum State: Equatable {\\n        case idle\\n        case executing(_ progress: CGFloat = 0)\\n        case finished(_ success: Bool = false)\\n\\n        private var keyPath:String{\\n            switch self {\\n            case .idle:\\n                return \\\"isIdle\\\"\\n            case .executing(_):\\n                return \\\"isExecuting\\\"\\n            case .finished(_):\\n                return \\\"isFinished\\\"\\n\\n            }\\n        }\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson17BaseOperation3\\n// $$liquidcoderFilename-BaseOperation\\n\\nimport SwiftUI\\n\\nclass BaseOperation: Operation {\\n\\n    enum State: Equatable {\\n        case idle\\n        case executing(_ progress: CGFloat = 0)\\n        case finished(_ success: Bool = false)\\n\\n        private var keyPath:String{\\n            switch self {\\n            case .idle:\\n                return \\\"isIdle\\\"\\n            case .executing(_):\\n                return \\\"isExecuting\\\"\\n            case .finished(_):\\n                return \\\"isFinished\\\"\\n\\n            }\\n        }\\n    }\\n\\n    // highlight-start\\n    var onStateChange: ((State) -> Void) = {_ in }\\n    // highlight-end\\n\\n    // highlight-start\\n    var state = State.idle {\\n        willSet{\\n            willChangeValue(forKey: newValue.keyPath)\\n            willChangeValue(forKey: state.keyPath)\\n        }\\n        didSet{\\n            onStateChange(state)\\n            didChangeValue(forKey: oldValue.keyPath)\\n            didChangeValue(forKey: state.keyPath)\\n        }\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson17BaseOperation4\\n// $$liquidcoderFilename-BaseOperation\\n\\nimport SwiftUI\\n\\nclass BaseOperation: Operation {\\n\\n        ...\\n\\n    // highlight-start\\n    override private var isExecuting: Bool{\\n\\n        if case .executing(_) = state {\\n            return true\\n        }\\n\\n        return false\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    override private var isFinished: Bool{\\n\\n        if case .finished(_) = state {\\n            return true\\n        }\\n\\n        return false\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    override private var isAsynchronous: Bool{\\n        return true\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson17BaseOperation5\\n// $$liquidcoderFilename-BaseOperation\\n\\nimport SwiftUI\\n\\nclass BaseOperation: Operation {\\n\\n        ...\\n\\n    override var isFinished: Bool{\\n\\n        if case .finished(_) = state {\\n            return true\\n        }\\n\\n        return false\\n    }\\n\\n    override var isAsynchronous: Bool{\\n        return true\\n    }\\n\\n    // highlight-start\\n    override func start() {\\n        if isCancelled {\\n            state = .finished()\\n            return\\n        }\\n        state = .executing()\\n        main()\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson17BaseOperation6\\n// $$liquidcoderFilename-BaseOperation\\n\\nimport SwiftUI\\n\\nclass BaseOperation: Operation {\\n\\n        ...\\n\\n    override var isAsynchronous: Bool{\\n        return true\\n    }\\n\\n    override func start() {\\n        if isCancelled {\\n            state = .finished()\\n            return\\n        }\\n        state = .executing()\\n        main()\\n    }\\n\\n    // highlight-start\\n    override func cancel() {\\n        state = .finished()\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson17DownloadOperation1\\n// $$liquidcoderFilename-DownloadOperation\\n\\nimport Foundation\\n\\nclass DownloadOperation: BaseOperation {\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson17DownloadOperation2\\n// $$liquidcoderFilename-DownloadOperation\\n\\nimport Foundation\\n\\nclass DownloadOperation: BaseOperation {\\n\\n    // highlight-start\\n    private var episode: EpisodeViewModel\\n    private var environment: AppEnvironment\\n    // highlight-end\\n\\n    // highlight-start\\n    init(episode: EpisodeViewModel, environment: AppEnvironment) {\\n        self.episode = episode\\n        self.environment = environment\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson17DownloadOperation3\\n// $$liquidcoderFilename-DownloadOperation\\n\\nimport Foundation\\n\\nclass DownloadOperation: BaseOperation {\\n\\n    private var episode: EpisodeViewModel\\n    private var environment: AppEnvironment\\n\\n    init(episode: EpisodeViewModel, environment: AppEnvironment) {\\n        self.episode = episode\\n        self.environment = environment\\n    }\\n\\n    // highlight-start\\n    override func main() {\\n\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    override func cancel() {\\n\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson17DownloadOperation4\\n// $$liquidcoderFilename-DownloadOperation\\n\\nimport Foundation\\n\\nclass DownloadOperation: BaseOperation {\\n\\n    private var episode: EpisodeViewModel\\n    private var environment: AppEnvironment\\n\\n    init(episode: EpisodeViewModel, environment: AppEnvironment) {\\n        self.episode = episode\\n        self.environment = environment\\n    }\\n\\n    override func main() {\\n\\n        // highlight-start\\n        if isCancelled || state == .finished() { return }\\n        // highlight-end\\n    }\\n\\n    override func cancel() {\\n\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson17DownloadOperation5\\n// $$liquidcoderFilename-DownloadOperation\\n\\nimport Foundation\\n\\nclass DownloadOperation: BaseOperation {\\n\\n    private var episode: EpisodeViewModel\\n    private var environment: AppEnvironment\\n\\n    init(episode: EpisodeViewModel, environment: AppEnvironment) {\\n        self.episode = episode\\n        self.environment = environment\\n    }\\n\\n    override func main() {\\n\\n        if isCancelled || state == .finished() { return }\\n\\n        // highlight-start\\n        guard let url = URL(string: episode.audioUrl) else {\\n            return\\n        }\\n        // highlight-end\\n\\n        // highlight-start\\n        self.name = episode.title\\n        // highlight-end\\n    }\\n\\n    override func cancel() {\\n\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson17UpdateAppEnvironment1\\n// $$liquidcoderFilename-AppEnvironment\\n\\nimport Foundation\\n\\nstruct AppEnvironment {\\n    let decoder: JSONDecoder\\n    let api: ApiServiceProtocol\\n    let coredata: CoredataServiceProtocol\\n    let rss: RssServiceProtocol\\n    // highlight-start\\n    let downloader: DownloaderServiceProtocol\\n    // highlight-end\\n\\n    init(decoder: JSONDecoder = .init(),\\n         api: ApiServiceProtocol = ApiService(),\\n         coredata: CoredataServiceProtocol = CoredataService(),\\n         rss: RssServiceProtocol = RssService(),\\n         // highlight-start\\n         downloader: DownloaderServiceProtocol = DownloaderService())\\n    // highlight-end\\n    {\\n        self.api = api\\n        self.decoder = decoder\\n        self.coredata = coredata\\n        self.rss = rss\\n        // highlight-start\\n        self.downloader = downloader\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson17DownloadOperation6\\n// $$liquidcoderFilename-DownloadOperation\\n\\nimport Foundation\\n\\nclass DownloadOperation: BaseOperation {\\n\\n    private var episode: EpisodeViewModel\\n    private var environment: AppEnvironment\\n\\n    init(episode: EpisodeViewModel, environment: AppEnvironment) {\\n        self.episode = episode\\n        self.environment = environment\\n    }\\n\\n    override func main() {\\n\\n        if isCancelled || state == .finished() { return }\\n\\n        guard let url = URL(string: episode.audioUrl) else {\\n            return\\n        }\\n\\n        self.name = episode.title\\n\\n        // highlight-start\\n        environment.downloader.download(from: url)\\n        // highlight-end\\n\\n        // highlight-start\\n        environment.downloader.didFinish = { data in\\n\\n            // save downloaded episode to coredata\\n        }\\n        // highlight-end\\n\\n        // highlight-start\\n        environment.downloader.progress = { value in\\n\\n            // update progress\\n        }\\n        // highlight-end\\n\\n    }\\n\\n    override func cancel() {\\n\\n    }\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson17Intro-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey guys, welcome back to lesson #17. In the previous one, we started working on the download feature, and in this one we will continue working on that mainly focusing on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Operation\"), \" class which is what we will use for threading.\")), mdx(\"div\", {\n    id: \"Lesson17BaseOperation0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Operation\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Apple says: \\\"An abstract class that represents the code and data associated with a single task.\\\"\"))), mdx(\"div\", {\n    id: \"Lesson17BaseOperation01-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Think of operations as little boxes that encapsulate some tasks to be executed when they are added to an operation queue, we will talk about this queue later\")), mdx(\"div\", {\n    id: \"Lesson17BaseOperation1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Base Operation\"), mdx(\"p\", null, \"We create a BaseOperation class that we make a subclass of the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Operation\"), \" class because  it's abstract which means we cannot  use it directly.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS, macOS and watchOS targets\"))), mdx(\"div\", {\n    id: \"Lesson17BaseOperation2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"This \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"State\"), \"  enum will help us keep track and maintain the progression state of a particular operation.\"), mdx(\"p\", null, \"We also create a property that returns a string to be used as \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"keypath\"), \" because we will make our \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"BaseOperation\"), \"   key-value observing compliant for the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" property in order for it to be observed.\")), mdx(\"div\", {\n    id: \"Lesson17BaseOperation3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onStateChange\"), \" closure will be called whenever the operation \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" changes.\"), mdx(\"p\", null, \"To support KVO (key-value observing), we need to call \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"willChangeValue\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"didChangeValue\"), \" with the old and new values' \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"keypath\"), \"s.\")), mdx(\"div\", {\n    id: \"Lesson17BaseOperation4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We then override the 3 properties, and in the first 2 we will observe the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" and return  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"true\"), \" if the case matches, otherwise we return \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"false\"), \". The last one just tells the operation to be asynchronous.\")), mdx(\"div\", {\n    id: \"Lesson17BaseOperation5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We need to override the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"start\"), \" method as well, and inside it we first check if the operation has been cancelled, if so, we update our local \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" and stop, otherwise we update the state to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"executing\"), \", and then we call \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"main\"), \".\"), mdx(\"p\", null, mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"main()\"), \" is the method in which the entire task will be done.\")), mdx(\"div\", {\n    id: \"Lesson17BaseOperation6-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Last, but not least we override the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cancel\"), \" method in which we just update the local \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"finished\"), \".\")), mdx(\"div\", {\n    id: \"Lesson17DownloadOperation1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Download Operation\"), mdx(\"p\", null, \"We have the base operation that we now subclass to create specialised operation to do just a single job.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS, macOS and watchOS targets\"))), mdx(\"div\", {\n    id: \"Lesson17DownloadOperation2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We will need the episode to download, and the environment that contains all of the necessary services to save the downloaded episode.\")), mdx(\"div\", {\n    id: \"Lesson17DownloadOperation3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next, we override the  2 methods that we need to execute the operation or cancel the running one\")), mdx(\"div\", {\n    id: \"Lesson17DownloadOperation4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"main\"), \", we first check whether the operation is cancelled or the state is \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"finished\"), \", the return if yes, otherwise we continue.\")), mdx(\"div\", {\n    id: \"Lesson17DownloadOperation5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next, we create a URL from the episode's audio url.\"), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Operation\"), \" has a name property that we set using the episode title, you will see why later on.\")), mdx(\"div\", {\n    id: \"Lesson17UpdateAppEnvironment1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Let's make a little U-Turn and update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppEnvironment\"), \" little quick. \"), mdx(\"p\", null, \"So here, we add another service which is the downloader service.\")), mdx(\"div\", {\n    id: \"Lesson17DownloadOperation6-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Having that service in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppEnvironment\"), \", we can now use it to do some work.\"), mdx(\"p\", null, \"So, we use it to start the download process, then below that we start listening to the 2 closures.\")), mdx(\"div\", {\n    id: \"Lesson17Outro-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And this bring the lesson #17 a close, but we are not done yet... In the next lesson (#18), we will see how to create and use multiple configurations in coredata while updating the coredata service to implement \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Podcast\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Episode\"), \" CRUD operations.\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#operation","title":"Operation"},{"url":"#base-operation","title":"Base Operation"},{"url":"#download-operation","title":"Download Operation"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey guys, welcome back to lesson #17. In the previous one, we started working on the download feature, and in this one we will continue working on that mainly focusing on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Operation\"), \" class which is what we will use for threading.\"), mdx(\"h2\", null, \"Operation\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Apple says: \\\"An abstract class that represents the code and data associated with a single task.\\\"\")), mdx(\"p\", null, \"Think of operations as little boxes that encapsulate some tasks to be executed when they are added to an operation queue, we will talk about this queue later\"), mdx(\"h2\", null, \"Base Operation\"), mdx(\"p\", null, \"We create a BaseOperation class that we make a subclass of the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Operation\"), \" class because  it's abstract which means we cannot  use it directly.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS, macOS and watchOS targets\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-BaseOperation\\n\\n// highlight-start\\nimport SwiftUI\\n// highlight-end\\n\\nclass BaseOperation: Operation {\\n\\n    // highlight-start\\n    enum State: Equatable {\\n        case idle\\n        case executing(_ progress: CGFloat = 0)\\n        case finished(_ success: Bool = false)\\n\\n        private var keyPath:String{\\n            switch self {\\n            case .idle:\\n                return \\\"isIdle\\\"\\n            case .executing(_):\\n                return \\\"isExecuting\\\"\\n            case .finished(_):\\n                return \\\"isFinished\\\"\\n\\n            }\\n        }\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"p\", null, \"This \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"State\"), \"  enum will help us keep track and maintain the progression state of a particular operation.\"), mdx(\"p\", null, \"We also create a property that returns a string to be used as \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"keypath\"), \" because we will make our \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"BaseOperation\"), \"   key-value observing compliant for the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" property in order for it to be observed.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-BaseOperation\\n\\nimport SwiftUI\\n\\nclass BaseOperation: Operation {\\n\\n    enum State: Equatable {\\n        case idle\\n        case executing(_ progress: CGFloat = 0)\\n        case finished(_ success: Bool = false)\\n\\n        fileprivate var keyPath:String{\\n            switch self {\\n            case .idle:\\n                return \\\"isIdle\\\"\\n            case .executing(_):\\n                return \\\"isExecuting\\\"\\n            case .finished(_):\\n                return \\\"isFinished\\\"\\n\\n            }\\n        }\\n    }\\n\\n    // highlight-start\\n    var onStateChange: ((State) -> Void) = {_ in }\\n    // highlight-end\\n\\n    // highlight-start\\n    var state = State.idle {\\n        willSet{\\n            willChangeValue(forKey: newValue.keyPath)\\n            willChangeValue(forKey: state.keyPath)\\n        }\\n        didSet{\\n            onStateChange(state)\\n            didChangeValue(forKey: oldValue.keyPath)\\n            didChangeValue(forKey: state.keyPath)\\n        }\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onStateChange\"), \" closure will be called whenever the operation \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" changes.\"), mdx(\"p\", null, \"To support KVO (key-value observing), we need to call \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"willChangeValue\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"didChangeValue\"), \" with the old and new values' \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"keypath\"), \"s.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-BaseOperation\\n\\nimport SwiftUI\\n\\nclass BaseOperation: Operation {\\n\\n        ...\\n\\n    // highlight-start\\n    override var isExecuting: Bool{\\n\\n        if case .executing(_) = state {\\n            return true\\n        }\\n\\n        return false\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    override var isFinished: Bool{\\n\\n        if case .finished(_) = state {\\n            return true\\n        }\\n\\n        return false\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    override var isAsynchronous: Bool{\\n        return true\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"p\", null, \"We then override the 3 properties, and in the first 2 we will observe the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" and return  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"true\"), \" if the case matches, otherwise we return \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"false\"), \". The last one just tells the operation to be asynchronous.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-BaseOperation\\n\\nimport SwiftUI\\n\\nclass BaseOperation: Operation {\\n\\n        ...\\n\\n    override var isFinished: Bool{\\n\\n        if case .finished(_) = state {\\n            return true\\n        }\\n\\n        return false\\n    }\\n\\n    override var isAsynchronous: Bool{\\n        return true\\n    }\\n\\n    // highlight-start\\n    override func start() {\\n        if isCancelled {\\n            state = .finished()\\n            return\\n        }\\n        state = .executing()\\n        main()\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"p\", null, \"We need to override the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"start\"), \" method as well, and inside it we first check if the operation has been cancelled, if so, we update our local \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" and stop, otherwise we update the state to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"executing\"), \", and then we call \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"main\"), \".\"), mdx(\"p\", null, mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"main()\"), \" is the method in which the entire task will be done.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-BaseOperation\\n\\nimport SwiftUI\\n\\nclass BaseOperation: Operation {\\n\\n        ...\\n\\n    override var isAsynchronous: Bool{\\n        return true\\n    }\\n\\n    override func start() {\\n        if isCancelled {\\n            state = .finished()\\n            return\\n        }\\n        state = .executing()\\n        main()\\n    }\\n\\n    // highlight-start\\n    override func cancel() {\\n        state = .finished()\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"p\", null, \"Last, but not least we override the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cancel\"), \" method in which we just update the local \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"finished\"), \".\"), mdx(\"h2\", null, \"Download Operation\"), mdx(\"p\", null, \"We have the base operation that we now subclass to create specialised operation to do just a single job.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS, macOS and watchOS targets\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-DownloadOperation\\n\\nimport Foundation\\n\\nclass DownloadOperation: BaseOperation {\\n\\n    // highlight-start\\n    private var episode: EpisodeViewModel\\n    private var environment: AppEnvironment\\n    // highlight-end\\n\\n    // highlight-start\\n    init(episode: EpisodeViewModel, environment: AppEnvironment) {\\n        self.episode = episode\\n        self.environment = environment\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"p\", null, \"We will need the episode to download, and the environment that contains all of the necessary services to save the downloaded episode.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-DownloadOperation\\n\\nimport Foundation\\n\\nclass DownloadOperation: BaseOperation {\\n\\n    private var episode: EpisodeViewModel\\n    private var environment: AppEnvironment\\n\\n    init(episode: EpisodeViewModel, environment: AppEnvironment) {\\n        self.episode = episode\\n        self.environment = environment\\n    }\\n\\n    // highlight-start\\n    override func main() {\\n\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    override func cancel() {\\n\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"p\", null, \"Next, we override the  2 methods that we need to execute the operation or cancel the running one\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-DownloadOperation\\n\\nimport Foundation\\n\\nclass DownloadOperation: BaseOperation {\\n\\n    private var episode: EpisodeViewModel\\n    private var environment: AppEnvironment\\n\\n    init(episode: EpisodeViewModel, environment: AppEnvironment) {\\n        self.episode = episode\\n        self.environment = environment\\n    }\\n\\n    override func main() {\\n\\n        // highlight-start\\n        if isCancelled || state == .finished() { return }\\n        // highlight-end\\n    }\\n\\n    override func cancel() {\\n\\n    }\\n}\\n\")), mdx(\"p\", null, \"In \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"main\"), \", we first check whether the operation is cancelled or the state is \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"finished\"), \", the return if yes, otherwise we continue.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-DownloadOperation\\n\\nimport Foundation\\n\\nclass DownloadOperation: BaseOperation {\\n\\n    private var episode: EpisodeViewModel\\n    private var environment: AppEnvironment\\n\\n    init(episode: EpisodeViewModel, environment: AppEnvironment) {\\n        self.episode = episode\\n        self.environment = environment\\n    }\\n\\n    override func main() {\\n\\n        if isCancelled || state == .finished() { return }\\n\\n        // highlight-start\\n        guard let url = URL(string: episode.audioUrl) else {\\n            return\\n        }\\n        // highlight-end\\n\\n        // highlight-start\\n        self.name = episode.title\\n        // highlight-end\\n    }\\n\\n    override func cancel() {\\n\\n    }\\n}\\n\")), mdx(\"p\", null, \"Next, we create a URL from the episode's audio url.\"), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Operation\"), \" has a name property that we set using the episode title, you will see why later on.\"), mdx(\"p\", null, \"Let's make a little U-Turn and update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppEnvironment\"), \" little quick. \"), mdx(\"p\", null, \"So here, we add another service which is the downloader service.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-AppEnvironment\\n\\nimport Foundation\\n\\nstruct AppEnvironment {\\n    let decoder: JSONDecoder\\n    let api: ApiServiceProtocol\\n    let coredata: CoredataServiceProtocol\\n    let rss: RssServiceProtocol\\n    // highlight-start\\n    let downloader: DownloaderServiceProtocol\\n    // highlight-end\\n\\n    init(decoder: JSONDecoder = .init(),\\n         api: ApiServiceProtocol = ApiService(),\\n         coredata: CoredataServiceProtocol = CoredataService(),\\n         rss: RssServiceProtocol = RssService(),\\n         // highlight-start\\n         downloader: DownloaderServiceProtocol = DownloaderService())\\n    // highlight-end\\n    {\\n        self.api = api\\n        self.decoder = decoder\\n        self.coredata = coredata\\n        self.rss = rss\\n        // highlight-start\\n        self.downloader = downloader\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-DownloadOperation\\n\\nimport Foundation\\n\\nclass DownloadOperation: BaseOperation {\\n\\n    private var episode: EpisodeViewModel\\n    private var environment: AppEnvironment\\n\\n    init(episode: EpisodeViewModel, environment: AppEnvironment) {\\n        self.episode = episode\\n        self.environment = environment\\n    }\\n\\n    override func main() {\\n\\n        if isCancelled || state == .finished() { return }\\n\\n        guard let url = URL(string: episode.audioUrl) else {\\n            return\\n        }\\n\\n        self.name = episode.title\\n\\n        // highlight-start\\n        environment.downloader.download(from: url)\\n        // highlight-end\\n\\n        // highlight-start\\n        environment.downloader.didFinish = { data in\\n\\n            // save downloaded episode to coredata\\n        }\\n        // highlight-end\\n\\n        // highlight-start\\n        environment.downloader.progress = { value in\\n\\n            // update progress\\n        }\\n        // highlight-end\\n\\n    }\\n\\n    override func cancel() {\\n\\n    }\\n}\\n\")), mdx(\"p\", null, \"Having that service in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppEnvironment\"), \", we can now use it to do some work.\"), mdx(\"p\", null, \"So, we use it to start the download process, then below that we start listening to the 2 closures.\"), mdx(\"p\", null, \"And this bring the lesson #17 a close, but we are not done yet... In the next lesson (#18), we will see how to create and use multiple configurations in coredata while updating the coredata service to implement \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Podcast\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Episode\"), \" CRUD operations.\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#operation","title":"Operation"},{"url":"#base-operation","title":"Base Operation"},{"url":"#download-operation","title":"Download Operation"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-03-10T09:16:44.491Z","isNewLayout":true,"previews":null,"slug":"lesson-18-multi-configurations-in-coredata","title":"Lesson #18: Multi-Configurations in Coredata","updatedDate":"2021-03-10T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZFHUfXZFL2teyW4vXSkgPxHdxvlJSC1KnDy","type":"zip","id":"ddce160f-8e13-52ad-a40c-1ddb64b65c76","name":"Source code"}],"allChapters":null,"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson18CoredataConfigurations0\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614075034/StreamCast/Lesson%2018/mea0mixdfqtkj8by4tjx.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614075034/StreamCast/Lesson%2018/mea0mixdfqtkj8by4tjx.png\"\n  }))))), mdx(\"div\", {\n    id: \"Lesson18CoredataConfigurations4\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614076104/StreamCast/Lesson%2018/pss8afrpsdaqtjkxkqpr.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614076104/StreamCast/Lesson%2018/pss8afrpsdaqtjkxkqpr.png\"\n  }))))), mdx(\"div\", {\n    id: \"Lesson18CoredataConfigurations5\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614076878/StreamCast/Lesson%2018/apddm3cnvaimjxx1fman.gif\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614076878/StreamCast/Lesson%2018/apddm3cnvaimjxx1fman.gif\"\n  }))))), mdx(\"div\", {\n    id: \"Lesson18CoredataConfigurations6\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614079281/StreamCast/Lesson%2018/phkw2csitoteqvyrqt8i.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614079281/StreamCast/Lesson%2018/phkw2csitoteqvyrqt8i.png\"\n  }))))), mdx(\"div\", {\n    id: \"Lesson18CoredataConfigurations8\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614080934/StreamCast/Lesson%2018/se5qnivsgrjy3sptwpb0.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614080934/StreamCast/Lesson%2018/se5qnivsgrjy3sptwpb0.png\"\n  }))))), mdx(\"div\", {\n    id: \"Lesson18CoredataConfigurations9\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614081553/StreamCast/Lesson%2018/dhxc6fntpysbv047hztf.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614081553/StreamCast/Lesson%2018/dhxc6fntpysbv047hztf.png\"\n  }))))), mdx(\"div\", {\n    id: \"Lesson18CoredataConfigurations12\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614082591/StreamCast/Lesson%2018/hxbgy8yxi48qfubh8lnf.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614082591/StreamCast/Lesson%2018/hxbgy8yxi48qfubh8lnf.png\"\n  }))))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson18UpdatePersistenceController1\\n// $$liquidcoderFilename-PersistenceController\\n\\nimport CoreData\\n\\nstruct PersistenceController {\\n    static let shared = PersistenceController()\\n    let container: NSPersistentCloudKitContainer\\n\\n    init(inMemory: Bool = false) {\\n        container = NSPersistentCloudKitContainer(name: \\\"StreamCast\\\")\\n\\n        // highlight-start\\n        let defaultDirectoryURL = NSPersistentContainer.defaultDirectoryURL()\\n        // highlight-end\\n\\n        container.loadPersistentStores(completionHandler: { (_, error) in\\n            if let error = error as NSError? {\\n                fatalError(\\\"Unresolved error \\\\(error), \\\\(error.userInfo)\\\")\\n            }\\n        })\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson18UpdatePersistenceController2\\n// $$liquidcoderFilename-PersistenceController\\n\\nimport CoreData\\n\\nstruct PersistenceController {\\n    static let shared = PersistenceController()\\n    let container: NSPersistentCloudKitContainer\\n\\n    init(inMemory: Bool = false) {\\n        container = NSPersistentCloudKitContainer(name: \\\"StreamCast\\\")\\n\\n        let defaultDirectoryURL = NSPersistentContainer.defaultDirectoryURL()\\n\\n        // highlight-start\\n         let cloudSyncStoreDescription = createDescription(configuration: \\\"CloudSync\\\", dirURL: defaultDirectoryURL)\\n         let localStoreDescription = createDescription(configuration: \\\"Local\\\", dirURL: defaultDirectoryURL)\\n         // highlight-end\\n\\n        container.loadPersistentStores(completionHandler: { (_, error) in\\n            if let error = error as NSError? {\\n                fatalError(\\\"Unresolved error \\\\(error), \\\\(error.userInfo)\\\")\\n            }\\n        })\\n    }\\n\\n    // highlight-start\\n    private func createDescription(configuration: String, dirURL: URL) -> NSPersistentStoreDescription {\\n        let dbUrl = dirURL.appendingPathComponent(\\\"\\\\(configuration).sqlite\\\")\\n        let storeDescription = NSPersistentStoreDescription(url: dbUrl)\\n        storeDescription.configuration = configuration\\n\\n        return storeDescription\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\n// $$liquidcoderID-Lesson18UpdatePersistenceController3\\n// $$liquidcoderFilename-PersistenceController\\n\\nimport CoreData\\n\\nstruct PersistenceController {\\n    static let shared = PersistenceController()\\n    let container: NSPersistentCloudKitContainer\\n\\n    init(inMemory: Bool = false) {\\n        container = NSPersistentCloudKitContainer(name: \\\"StreamCast\\\")\\n\\n        let defaultDirectoryURL = NSPersistentContainer.defaultDirectoryURL()\\n\\n         let cloudSyncStoreDescription = createDescription(configuration: \\\"CloudSync\\\", dirURL: defaultDirectoryURL)\\n         let localStoreDescription = createDescription(configuration: \\\"Local\\\", dirURL: defaultDirectoryURL)\\n\\n        // highlight-start\\n        container.persistentStoreDescriptions = [cloudSyncStoreDescription, localStoreDescription]\\n        // highlight-end\\n\\n        container.loadPersistentStores(completionHandler: { (_, error) in\\n            if let error = error as NSError? {\\n                fatalError(\\\"Unresolved error \\\\(error), \\\\(error.userInfo)\\\")\\n            }\\n        })\\n    }\\n\\n    private func createDescription(configuration: String, dirURL: URL) -> NSPersistentStoreDescription {\\n        let dbUrl = dirURL.appendingPathComponent(\\\"\\\\(configuration).sqlite\\\")\\n        let storeDescription = NSPersistentStoreDescription(url: dbUrl)\\n        storeDescription.configuration = configuration\\n\\n        return storeDescription\\n    }\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson18Intro-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey guys welcome back to lesson #18. In the previous one, we continued working on the download feature creating the downloader operation. In this one, we will work on the persistence side of the feature. let's get started!\")), mdx(\"div\", {\n    id: \"Lesson18CoredataConfigurations0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Coredata configurations\"), mdx(\"p\", null, \"As of right now, we only have one configuration which is the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Default\"), \" one, and it is , by default, used with \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"CloudKit\"), \" because we've check the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"\\\"Host in CloudKit\\\"\"), \" when creating the project.\"), mdx(\"p\", null, \"And you can see, the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"GenreManagedObject\"), \" is automatically included in that configuration.\")), mdx(\"div\", {\n    id: \"Lesson18CoredataConfigurations1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Why do we need to create new configurations? And why not use the existing one? Well, we want to only sync \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Podcasts\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Episodes\"), \" in iCloud, and leave \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Genres\"), \" to be local. \"), mdx(\"p\", null, \"The reason of doing this is the slowness of \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"iCloud sync\"), \". See, it takes a lot of time for items in iCloud to be fetch and merge locally, it is not realtime process, and we would not want our users to wait too long on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreSelectionScreen\"), \" when they switch to a different device using the same \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"iCloud\"), \" account, would we?\")), mdx(\"div\", {\n    id: \"Lesson18CoredataConfigurations3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We will create 2 more configurations named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"CloudSync\"), \" and \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Local.\"))), mdx(\"div\", {\n    id: \"Lesson18CoredataConfigurations4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"So to do that, Select:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Editor\"), \" menu\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Add Configuration\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Name the Configuration \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"CloudSync\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Check the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Used with CloudKit\"), \" box\")), mdx(\"p\", null, \"Now do the same thing for the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Local\"), \" one, but don't check the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Used with CloudKit\"), \" box. \")), mdx(\"div\", {\n    id: \"Lesson18CoredataConfigurations5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next, you will drag and drop the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"GenreManagedObject\"), \" entity from the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Entities\"), \" section to the Configurations section, and add it to the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Local\"), \" configuration.\")), mdx(\"div\", {\n    id: \"Lesson18CoredataConfigurations6-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next up, let's create the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PodcastManagedObject\"), \" and \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"EpisodeManagedObject.\")), mdx(\"p\", null, \"Now go ahead and add those 2 entities along with their attributes as seen in the image.\")), mdx(\"div\", {\n    id: \"Lesson18CoredataConfigurations7-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"After you've finished adding the 2 entities, the next step is to setup their relationships, but first make sure you move the 2 entities in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"CloudSync\"), \" configuration. Make sure there are only 2 entities in \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"CloudSync\"), \" and one in  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Local.\"), \" \"), mdx(\"p\", null, \"Don't worry about the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Default\"), \" configuration containing all 3 entities, you cannot delete the entities in there, we will just not use the entire configuration.\")), mdx(\"div\", {\n    id: \"Lesson18CoredataConfigurations8-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Let's start with the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PodcastManagedObject.\"), \" \"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"While the \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"PodcastManagedObject\"), \" entity is selected, \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Expand the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Relationships\"), \" section, and add a new relation named  \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"episodes,\"), \" \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then choose the destination as \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"EpisodeManagedObject\"), \" in the dropdown. \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Last, change the type to \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"\\\"To Many\\\"\"), \" which means a single podcast will have one or more episodes.\"))), mdx(\"div\", {\n    id: \"Lesson18CoredataConfigurations9-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Let's close the relationship with the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"EpisodeManagedObject\")), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"While the \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"EpisodeManagedObject\"), \" entity is selected, \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Expand the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Relationships\"), \" section, and add a new relation named  \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"podcast,\"), \" \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then choose the destination as \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"PodcastManagedObject\"), \" in the dropdown. \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Make sure the inverse is episodes\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"And Last, change the type to \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"\\\"To One\\\"\"), \" which means a single episode will only have one parent podcast\"))), mdx(\"div\", {\n    id: \"Lesson18CoredataConfigurations10-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"There are a bunch of different ways we can save binary data ( images, videos and audios)  in an \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Xcode\"), \" project , one would be to save the data to the file system, then store its path url to coredata, and another is to use coredata with external storage usage allowed. We will use the latter.\")), mdx(\"div\", {\n    id: \"Lesson18CoredataConfigurations11-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Let's make some more changes to our entities to support external storage usage for attributes that have binary data as type.\")), mdx(\"div\", {\n    id: \"Lesson18CoredataConfigurations12-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"To support external storage, follow this:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Select the entity, this example is for the \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"EpisodeManagedObject\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then select the attribute with type of \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"Binary Data,\"), \" you may need to double-click on the name to show the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Data Model Inspector.\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Click the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Data Model Inspector\"), \" tab if it did not appear in step 2\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then, check the \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"Allows External Storage\"), \" to tell coredata that you want the data associated with this attribute to be saved to the file system .\"))), mdx(\"div\", {\n    id: \"Lesson18CoredataConfigurations13-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now, make sure to do the same thing for the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"image\"), \" and \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"thumbnail\"), \" in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PodcastManagedObject\"), \" entity.\"), mdx(\"p\", null, \"Then \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Command + B (CMD + B)\"), \" to build the project.\")), mdx(\"div\", {\n    id: \"Lesson18CoredataConfigurations14-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next, let's configure the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NSPersistentCloudKitContainer\"), \" in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PersistentController\"), \" file to use the 2 configurations we have just added.\")), mdx(\"div\", {\n    id: \"Lesson18UpdatePersistenceController1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Open the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PersistentController\"), \" file, and add the highlighted line of code.\"), mdx(\"p\", null, \"Here, we get the default directory url where the database is located.\")), mdx(\"div\", {\n    id: \"Lesson18UpdatePersistenceController2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We then create a store description for each of the 2 configurations.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"As per the apple developer docs: \\\"A store description is an object used to create and load a persistent store.\\\"\")), mdx(\"p\", null, \"I call it a blueprint to create a persistent store (database) in coredata.\")), mdx(\"div\", {\n    id: \"Lesson18UpdatePersistenceController21-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createDescription\"), \" function, we create a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"dbUrl\"), \" for the provided configuration, then with that we create the description, and last we set the provided configuration to the description's configuration\")), mdx(\"div\", {\n    id: \"Lesson18UpdatePersistenceController3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Last, but not least, we override the default  configuration by setting that \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"persistentStoreDescriptions\"), \" of the container giving it the 2 descriptions we've just created.\")), mdx(\"div\", {\n    id: \"Lesson18Outro-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And this brings this lesson to a close. In the next lesson which is #19, we will continue from where left off with the download process.\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#coredata-configurations","title":"Coredata configurations"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey guys welcome back to lesson #18. In the previous one, we continued working on the download feature creating the downloader operation. In this one, we will work on the persistence side of the feature. let's get started!\"), mdx(\"h2\", null, \"Coredata configurations\"), mdx(\"p\", null, \"As of right now, we only have one configuration which is the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Default\"), \" one, and it is , by default, used with \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"CloudKit\"), \" because we've check the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"\\\"Host in CloudKit\\\"\"), \" when creating the project.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614075034/StreamCast/Lesson%2018/mea0mixdfqtkj8by4tjx.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614075034/StreamCast/Lesson%2018/mea0mixdfqtkj8by4tjx.png\"\n  }))), mdx(\"p\", null, \"And you can see, the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"GenreManagedObject\"), \" is automatically included in that configuration.\"), mdx(\"p\", null, \"Why do we need to create new configurations? And why not use the existing one? Well, we want to only sync \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Podcasts\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Episodes\"), \" in iCloud, and leave \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Genres\"), \" to be local. \"), mdx(\"p\", null, \"The reason of doing this is the slowness of \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"iCloud sync\"), \". See, it takes a lot of time for items in iCloud to be fetch and merge locally, it is not realtime process, and we would not want our users to wait too long on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenreSelectionScreen\"), \" when they switch to a different device using the same \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"iCloud\"), \" account, would we?\"), mdx(\"p\", null, \"We will create 2 more configurations named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"CloudSync\"), \" and \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Local.\")), mdx(\"p\", null, \"So to do that, Select:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Editor\"), \" menu\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Add Configuration\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Name the Configuration \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"CloudSync\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Check the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Used with CloudKit\"), \" box\")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614076104/StreamCast/Lesson%2018/pss8afrpsdaqtjkxkqpr.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614076104/StreamCast/Lesson%2018/pss8afrpsdaqtjkxkqpr.png\"\n  }))), mdx(\"p\", null, \"Now do the same thing for the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Local\"), \" one, but don't check the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Used with CloudKit\"), \" box. \"), mdx(\"p\", null, \"Next, you will drag and drop the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"GenreManagedObject\"), \" entity from the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Entities\"), \" section to the Configurations section, and add it to the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Local\"), \" configuration.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614076878/StreamCast/Lesson%2018/apddm3cnvaimjxx1fman.gif\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614076878/StreamCast/Lesson%2018/apddm3cnvaimjxx1fman.gif\"\n  }))), mdx(\"p\", null, \"Next up, let's create the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PodcastManagedObject\"), \" and \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"EpisodeManagedObject.\")), mdx(\"p\", null, \"Now go ahead and add those 2 entities along with their attributes as seen in the image.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614079281/StreamCast/Lesson%2018/phkw2csitoteqvyrqt8i.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614079281/StreamCast/Lesson%2018/phkw2csitoteqvyrqt8i.png\"\n  }))), mdx(\"p\", null, \"After you've finished adding the 2 entities, the next step is to setup their relationships, but first make sure you move the 2 entities in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"CloudSync\"), \" configuration. Make sure there are only 2 entities in \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"CloudSync\"), \" and one in  \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Local.\"), \" \"), mdx(\"p\", null, \"Don't worry about the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Default\"), \" configuration containing all 3 entities, you cannot delete the entities in there, we will just not use the entire configuration.\"), mdx(\"p\", null, \"Let's start with the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PodcastManagedObject.\"), \" \"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"While the \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"PodcastManagedObject\"), \" entity is selected, \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Expand the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Relationships\"), \" section, and add a new relation named  \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"episodes,\"), \" \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then choose the destination as \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"EpisodeManagedObject\"), \" in the dropdown. \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Last, change the type to \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"\\\"To Many\\\"\"), \" which means a single podcast will have one or more episodes.\")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614080934/StreamCast/Lesson%2018/se5qnivsgrjy3sptwpb0.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614080934/StreamCast/Lesson%2018/se5qnivsgrjy3sptwpb0.png\"\n  }))), mdx(\"p\", null, \"Let's close the relationship with the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"EpisodeManagedObject\")), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"While the \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"EpisodeManagedObject\"), \" entity is selected, \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Expand the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Relationships\"), \" section, and add a new relation named  \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"podcast,\"), \" \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then choose the destination as \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"PodcastManagedObject\"), \" in the dropdown. \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Make sure the inverse is episodes\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"And Last, change the type to \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"\\\"To One\\\"\"), \" which means a single episode will only have one parent podcast\")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614081553/StreamCast/Lesson%2018/dhxc6fntpysbv047hztf.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614081553/StreamCast/Lesson%2018/dhxc6fntpysbv047hztf.png\"\n  }))), mdx(\"p\", null, \"There are a bunch of different ways we can save binary data ( images, videos and audios)  in an \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Xcode\"), \" project , one would be to save the data to the file system, then store its path url to coredata, and another is to use coredata with external storage usage allowed. We will use the latter.\"), mdx(\"p\", null, \"Let's make some more changes to our entities to support external storage usage for attributes that have binary data as type.\"), mdx(\"p\", null, \"To support external storage, follow this:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Select the entity, this example is for the \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"EpisodeManagedObject\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then select the attribute with type of \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"Binary Data,\"), \" you may need to double-click on the name to show the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Data Model Inspector.\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Click the \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Data Model Inspector\"), \" tab if it did not appear in step 2\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then, check the \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"Allows External Storage\"), \" to tell coredata that you want the data associated with this attribute to be saved to the file system .\")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614082591/StreamCast/Lesson%2018/hxbgy8yxi48qfubh8lnf.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1614082591/StreamCast/Lesson%2018/hxbgy8yxi48qfubh8lnf.png\"\n  }))), mdx(\"p\", null, \"Now, make sure to do the same thing for the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"image\"), \" and \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"thumbnail\"), \" in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PodcastManagedObject\"), \" entity.\"), mdx(\"p\", null, \"Then \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Command + B (CMD + B)\"), \" to build the project.\"), mdx(\"p\", null, \"Next, let's configure the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NSPersistentCloudKitContainer\"), \" in the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PersistentController\"), \" file to use the 2 configurations we have just added.\"), mdx(\"p\", null, \"Open the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PersistentController\"), \" file, and add the highlighted line of code.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson18UpdatePersistenceController1\\n// $$liquidcoderFilename-PersistenceController\\n\\nimport CoreData\\n\\nstruct PersistenceController {\\n    static let shared = PersistenceController()\\n    let container: NSPersistentCloudKitContainer\\n\\n    init(inMemory: Bool = false) {\\n        container = NSPersistentCloudKitContainer(name: \\\"StreamCast\\\")\\n\\n        // highlight-start\\n        let defaultDirectoryURL = NSPersistentContainer.defaultDirectoryURL()\\n        // highlight-end\\n\\n        container.loadPersistentStores(completionHandler: { (_, error) in\\n            if let error = error as NSError? {\\n                fatalError(\\\"Unresolved error \\\\(error), \\\\(error.userInfo)\\\")\\n            }\\n        })\\n    }\\n}\\n\")), mdx(\"p\", null, \"Here, we get the default directory url where the database is located.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson18UpdatePersistenceController2\\n// $$liquidcoderFilename-PersistenceController\\n\\nimport CoreData\\n\\nstruct PersistenceController {\\n    static let shared = PersistenceController()\\n    let container: NSPersistentCloudKitContainer\\n\\n    init(inMemory: Bool = false) {\\n        container = NSPersistentCloudKitContainer(name: \\\"StreamCast\\\")\\n\\n        let defaultDirectoryURL = NSPersistentContainer.defaultDirectoryURL()\\n\\n        // highlight-start\\n         let cloudSyncStoreDescription = createDescription(configuration: \\\"CloudSync\\\", dirURL: defaultDirectoryURL)\\n         let localStoreDescription = createDescription(configuration: \\\"Local\\\", dirURL: defaultDirectoryURL)\\n         // highlight-end\\n\\n        container.loadPersistentStores(completionHandler: { (_, error) in\\n            if let error = error as NSError? {\\n                fatalError(\\\"Unresolved error \\\\(error), \\\\(error.userInfo)\\\")\\n            }\\n        })\\n    }\\n\\n    // highlight-start\\n    private func createDescription(configuration: String, dirURL: URL) -> NSPersistentStoreDescription {\\n        let dbUrl = dirURL.appendingPathComponent(\\\"\\\\(configuration).sqlite\\\")\\n        let storeDescription = NSPersistentStoreDescription(url: dbUrl)\\n        storeDescription.configuration = configuration\\n\\n        return storeDescription\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"p\", null, \"We then create a store description for each of the 2 configurations.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"As per the apple developer docs: \\\"A store description is an object used to create and load a persistent store.\\\"\")), mdx(\"p\", null, \"I call it a blueprint to create a persistent store (database) in coredata.\"), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createDescription\"), \" function, we create a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"dbUrl\"), \" for the provided configuration, then with that we create the description, and last we set the provided configuration to the description's configuration\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson18UpdatePersistenceController3\\n// $$liquidcoderFilename-PersistenceController\\n\\nimport CoreData\\n\\nstruct PersistenceController {\\n    static let shared = PersistenceController()\\n    let container: NSPersistentCloudKitContainer\\n\\n    init(inMemory: Bool = false) {\\n        container = NSPersistentCloudKitContainer(name: \\\"StreamCast\\\")\\n\\n        let defaultDirectoryURL = NSPersistentContainer.defaultDirectoryURL()\\n\\n         let cloudSyncStoreDescription = createDescription(configuration: \\\"CloudSync\\\", dirURL: defaultDirectoryURL)\\n         let localStoreDescription = createDescription(configuration: \\\"Local\\\", dirURL: defaultDirectoryURL)\\n\\n        // highlight-start\\n        container.persistentStoreDescriptions = [cloudSyncStoreDescription, localStoreDescription]\\n        // highlight-end\\n\\n        container.loadPersistentStores(completionHandler: { (_, error) in\\n            if let error = error as NSError? {\\n                fatalError(\\\"Unresolved error \\\\(error), \\\\(error.userInfo)\\\")\\n            }\\n        })\\n    }\\n\\n    private func createDescription(configuration: String, dirURL: URL) -> NSPersistentStoreDescription {\\n        let dbUrl = dirURL.appendingPathComponent(\\\"\\\\(configuration).sqlite\\\")\\n        let storeDescription = NSPersistentStoreDescription(url: dbUrl)\\n        storeDescription.configuration = configuration\\n\\n        return storeDescription\\n    }\\n}\\n\")), mdx(\"p\", null, \"Last, but not least, we override the default  configuration by setting that \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"persistentStoreDescriptions\"), \" of the container giving it the 2 descriptions we've just created.\"), mdx(\"p\", null, \"And this brings this lesson to a close. In the next lesson which is #19, we will continue from where left off with the download process.\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#coredata-configurations","title":"Coredata configurations"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-03-12T09:01:22.438Z","isNewLayout":true,"previews":null,"slug":"lesson-19-download-manager","title":"Lesson #19: Download Manager","updatedDate":"2021-03-12T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZaYXBXZwLDvCKUugiphQSGbIQHnrzOgoaXk","type":"zip","id":"26e8a57b-b3d4-55bc-9bda-a44253ca33c9","name":"Source code"}],"allChapters":null,"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson19UpdateCoredataServiceProtocol1\\n// $$liquidcoderFilename-CoredataServiceProtocol\\n\\nimport Foundation\\n\\nprotocol CoredataServiceProtocol {\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool\\n    func fetchGenres() -> [GenreViewModel]\\n    // highlight-start\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson19UpdatePreviewCoredataService1\\n// $$liquidcoderFilename-PreviewCoredataService\\n\\nimport CoreData\\n\\nclass PreviewCoredataService: CoredataServiceProtocol {\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool {\\n\\n        return true\\n    }\\n\\n    func fetchGenres() -> [GenreViewModel] {\\n        return GenreViewModel.listPlaceholders\\n    }\\n\\n    // highlight-start\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool {\\n        return false\\n    }\\n    // highlight-end\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson19UpdateCoredataService1\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n        ...\\n\\n    // highlight-start\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool {\\n        return false\\n    }\\n    // highlight-end\\n\\n    private func saveContext() -> Bool {\\n        do {\\n            if context.hasChanges {\\n                try context.save()\\n            }\\n        } catch {\\n            let nsError = error as NSError\\n            fatalError(\\\"Unresolved error \\\\(nsError), \\\\(nsError.userInfo)\\\")\\n        }\\n\\n        return true\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson19UpdateDownloadOperation2\\n// $$liquidcoderFilename-DownloadOperation\\n\\nimport Foundation\\n\\nclass DownloadOperation: BaseOperation {\\n\\n    ...\\n\\n    override func main() {\\n\\n        ...\\n\\n        environment.downloader.didFinish = { [weak self] data in\\n\\n            // highlight-start\\n            guard let self = self,\\n                  let audio = data else { return}\\n            // highlight-end\\n\\n            // highlight-start\\n            if self.environment.coredata.saveEpisode(self.episode, audio: audio) {\\n                self.state = .finished(true)\\n            } else {\\n                self.state = .finished(false)\\n            }\\n            // highlight-end\\n\\n        }\\n\\n        environment.downloader.progress = { [weak self] value in\\n\\n            // update progress\\n\\n        }\\n\\n    }\\n\\n    override func cancel() {\\n\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson19UpdateDownloadOperation3\\n// $$liquidcoderFilename-DownloadOperation\\n\\nimport Foundation\\n\\nclass DownloadOperation: BaseOperation {\\n\\n    ...\\n\\n    override func main() {\\n\\n        ...\\n\\n        environment.downloader.progress = { [weak self] value in\\n\\n            // highlight-start\\n            self?.state = .executing(value)\\n            // highlight-end\\n\\n        }\\n\\n    }\\n\\n    override func cancel() {\\n        // highlight-start\\n        super.cancel()\\n        self.environment.downloader.cancel()\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson19DownloadManager1\\n// $$liquidcoderFilename-DownloadManager\\n\\nimport SwiftUI\\n\\n// highlight-start\\nclass DownloadManager: ObservableObject {\\n\\n}\\n// highlight-end\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson19DownloadManager2\\n// $$liquidcoderFilename-DownloadManager\\n\\nimport SwiftUI\\n\\nclass DownloadManager: ObservableObject {\\n\\n    // highlight-start\\n    enum State: Equatable {\\n        case idle\\n        case pending\\n        case downloading(progress: CGFloat)\\n        case done(success: Bool)\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson19DownloadManager3\\n// $$liquidcoderFilename-DownloadManager\\n\\nimport SwiftUI\\n\\nclass DownloadManager: ObservableObject {\\n\\n    enum State: Equatable {\\n        case idle\\n        case pending\\n        case downloading(progress: CGFloat)\\n        case done(success: Bool)\\n    }\\n\\n    // highlight-start\\n    @Published var state: State = .idle\\n    @Published var downloadings: [String: State] = [:]\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson19DownloadManager4\\n// $$liquidcoderFilename-DownloadManager\\n\\nimport SwiftUI\\n\\nclass DownloadManager: ObservableObject {\\n\\n    enum State: Equatable {\\n        case idle\\n        case pending\\n        case downloading(progress: CGFloat)\\n        case done(success: Bool)\\n    }\\n\\n    @Published var state: State = .idle\\n    @Published var downloadings: [String: State] = [:]\\n\\n    // highlight-start\\n    private var operationQueue = OperationQueue()\\n    // highlight-end\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson19DownloadManager41\\n// $$liquidcoderFilename-DownloadManager\\n\\nimport SwiftUI\\n\\nclass DownloadManager: ObservableObject {\\n\\n    enum State: Equatable {\\n        case idle\\n        case pending\\n        case downloading(progress: CGFloat)\\n        case done(success: Bool)\\n    }\\n\\n    @Published var state: State = .idle\\n    @Published var downloadings: [String: State] = [:]\\n\\n    private var operationQueue = OperationQueue()\\n\\n    // highlight-start\\n    private var environment: AppEnvironment\\n    // highlight-end\\n\\n    // highlight-start\\n    init(environment: AppEnvironment = AppEnvironment()) {\\n        operationQueue.maxConcurrentOperationCount = 1\\n        operationQueue.qualityOfService = .background\\n\\n        self.environment = environment\\n    }\\n    // highlight-end\\n\\n        // highlight-start\\n    func download(_ episode: EpisodeViewModel) {\\n\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson19DownloadManager5\\n// $$liquidcoderFilename-DownloadManager\\n\\nimport SwiftUI\\n\\nclass DownloadManager: ObservableObject {\\n\\n    init(environment: AppEnvironment = AppEnvironment()) {\\n\\n        operationQueue.maxConcurrentOperationCount = 1\\n        operationQueue.qualityOfService = .background\\n\\n        self.environment = environment\\n    }\\n\\n    ...\\n\\n    func download(_ episode: EpisodeViewModel) {\\n\\n        // highlight-start\\n        self.downloadings[episode.title] = .pending\\n        // highlight-end\\n\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson19DownloadManager6\\n// $$liquidcoderFilename-DownloadManager\\n\\nimport SwiftUI\\n\\nclass DownloadManager: ObservableObject {\\n\\n    ...\\n\\n    func download(_ episode: EpisodeViewModel) {\\n\\n        self.downloadings[episode.title] = .pending\\n\\n        let operation = DownloadOperation(episode: episode, environment: environment)\\n\\n        // highlight-start\\n        operation.onStateChange = { [weak self] state in\\n            switch state {\\n            case .idle:\\n                break\\n            case.executing(let progress):\\n                break\\n            case .finished(let success):\\n                break\\n            }\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson19DownloadManager7\\n// $$liquidcoderFilename-DownloadManager\\n\\nimport SwiftUI\\n\\nclass DownloadManager: ObservableObject {\\n\\n    ...\\n\\n    func download(_ episode: EpisodeViewModel) {\\n\\n        self.downloadings[episode.title] = .pending\\n\\n        let operation = DownloadOperation(episode: episode, environment: environment)\\n\\n        operation.onStateChange = { [weak self] state in\\n            switch state {\\n            case .idle:\\n                // highlight-start\\n                self?.downloadings[episode.title] = .pending\\n                // highlight-end\\n            case.executing(let progress):\\n                // highlight-start\\n                DispatchQueue.main.async {\\n                    self?.downloadings[episode.title] = .downloading(progress: progress)\\n                }\\n                // highlight-end\\n            case .finished(let success):\\n                // highlight-start\\n                DispatchQueue.main.async {\\n                    self?.state = .done(success: success)\\n                    self?.downloadings.removeValue(forKey: episode.title)\\n                }\\n                // highlight-end\\n            }\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson19DownloadManager8\\n// $$liquidcoderFilename-DownloadManager\\n\\nimport SwiftUI\\n\\nclass DownloadManager: ObservableObject {\\n\\n    ...\\n\\n    func download(_ episode: EpisodeViewModel) {\\n\\n        self.downloadings[episode.title] = .pending\\n\\n        let operation = DownloadOperation(episode: episode, environment: environment)\\n\\n        operation.onStateChange = { [weak self] state in\\n            switch state {\\n            case .idle:\\n                self?.downloadings[episode.title] = .pending\\n            case.executing(let progress):\\n                DispatchQueue.main.async {\\n                    self?.downloadings[episode.title] = .downloading(progress: progress)\\n                }\\n            case .finished(let success):\\n                DispatchQueue.main.async {\\n                    self?.state = .done(success: success)\\n                    self?.downloadings.removeValue(forKey: episode.title)\\n                }\\n            }\\n        }\\n\\n        // highlight-start\\n        operationQueue.addOperation(operation)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson19DownloadManager8\\n// $$liquidcoderFilename-DownloadManager\\n\\nimport SwiftUI\\n\\nclass DownloadManager: ObservableObject {\\n\\n    ...\\n\\n    func download(_ episode: EpisodeViewModel) {\\n\\n        self.downloadings[episode.title] = .pending\\n\\n        let operation = DownloadOperation(episode: episode, environment: environment)\\n\\n        operation.onStateChange = { [weak self] state in\\n            switch state {\\n            case .idle:\\n                self?.downloadings[episode.title] = .pending\\n            case.executing(let progress):\\n                DispatchQueue.main.async {\\n                    self?.downloadings[episode.title] = .downloading(progress: progress)\\n                }\\n            case .finished(let success):\\n                DispatchQueue.main.async {\\n                    self?.state = .done(success: success)\\n                    self?.downloadings.removeValue(forKey: episode.title)\\n                }\\n            }\\n        }\\n // highlight-start\\n        operationQueue.addOperation(operation)\\n         // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson19DownloadManager9\\n// $$liquidcoderFilename-DownloadManager\\n\\nimport SwiftUI\\n\\nclass DownloadManager: ObservableObject {\\n\\n    ...\\n\\n    func download(_ episode: EpisodeViewModel) {\\n\\n        self.downloadings[episode.title] = .pending\\n\\n        let operation = DownloadOperation(episode: episode, environment: environment)\\n\\n        operation.onStateChange = { [weak self] state in\\n            switch state {\\n            case .idle:\\n                self?.downloadings[episode.title] = .pending\\n            case.executing(let progress):\\n                DispatchQueue.main.async {\\n                    self?.downloadings[episode.title] = .downloading(progress: progress)\\n                }\\n            case .finished(let success):\\n                DispatchQueue.main.async {\\n                    self?.state = .done(success: success)\\n                    self?.downloadings.removeValue(forKey: episode.title)\\n                }\\n            }\\n        }\\n\\n        operationQueue.addOperation(operation)\\n    }\\n\\n    // highlight-start\\n    func abort(name: String) {\\n        if let operation = operationQueue.operations.first(where: {  $0.name == name }) {\\n            operation.cancel()\\n        }\\n    }\\n    // highlight-end\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson19Intro-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduct\"), mdx(\"p\", null, \"Hey guys, welcome back to lesson #19. Our app is coming along which is awesome. In this lesson, we will make few coredata related updates before continuing with the download feature. Let's go!\")), mdx(\"div\", {\n    id: \"Lesson19UpdateCoredataServiceProtocol1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the CoredataServiceProtocol\"), mdx(\"p\", null, \"We will update the coredata service protocol before updating the service itself, so add that line in there.\")), mdx(\"div\", {\n    id: \"Lesson19UpdatePreviewCoredataService1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the PreviewCoredataService\"), mdx(\"p\", null, \"We are also obliged to always update the preview coredata service whenever we change the protocol.\")), mdx(\"div\", {\n    id: \"Lesson19UpdateCoredataService1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the CoredataService\"), mdx(\"p\", null, \"And finally, add update the coredata service. \"), mdx(\"p\", null, \"We will return false for now, and go back to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DownloadOperation\"), \" class to finish with that class.\")), mdx(\"div\", {\n    id: \"Lesson19UpdateDownloadOperation1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Continuing with the DownloadOperation\"), mdx(\"p\", null, \"Now that we have a method that will save the downloaded episode, let's now be done the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DownloadOperation\"), \"  class.\")), mdx(\"div\", {\n    id: \"Lesson19UpdateDownloadOperation2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"didFinish\"), \", we first check if the audio data is valid, otherwise we return.\"), mdx(\"p\", null, \"If we have a valid audio, we save the episode in coredata, then depending on whether the operation was successful, we set the state to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"finished\"), \" .\")), mdx(\"div\", {\n    id: \"Lesson19UpdateDownloadOperation3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"progress\"), \" closure, we update the state with the progress.\"), mdx(\"p\", null, \"And in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cancel\"), \", we first call \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cancel\"), \" from the super class, and cancel the download process in the Downloader service class.\")), mdx(\"div\", {\n    id: \"Lesson19DownloadManager0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Download Manager\"), mdx(\"p\", null, \"The downloader manager is the class that will, as its name implies, manage all of the download processes. It will act like the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Store\"), \" , but it will only have one job.\")), mdx(\"div\", {\n    id: \"Lesson19DownloadManager1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Networking\")), \" folder, add a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"DownloadManager.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS, macOS and watchOS-Extensions targets.\"))), mdx(\"div\", {\n    id: \"Lesson19DownloadManager2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We create yet another \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"State\"), \" enum that will be used by views, we have added an extra case, the pending one, that will be active when a particular operation is in pending state meaning that when the operation order in the queue exceed the maximum count of running operations .\")), mdx(\"div\", {\n    id: \"Lesson19DownloadManager3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We then declare a state property, as well as a downloadings dictionary property. Both having the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"@Published\"), \" property wrapper meaning that they will be observed by other views in the app.\"), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"downloadings\"), \" dictionary will accept strings as keys and states as values. The keys will operations.\")), mdx(\"div\", {\n    id: \"Lesson19DownloadManager4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Remember that I said operations are like boxes containing pieces of tasks to be executed, well the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"operationQueue\"), \" will be their coordinator. \")), mdx(\"div\", {\n    id: \"Lesson19DownloadManager41-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"initialiser\"), \", we set the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"maxConcurrentOperationCount\"), \" to 1 to limit only 1 download process at a time. This will be dynamic based on the type of connexion we are currently using.\")), mdx(\"div\", {\n    id: \"Lesson19DownloadManager5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"download\"), \", we add the episode that is about to be downloaded to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"downloadings\"), \" dictionary, setting its state to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"pending\"), \".\")), mdx(\"div\", {\n    id: \"Lesson19DownloadManager6-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Remember the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onStateChange\"), \" closure?  It will be updated whenever the state changes, and in turn it we will notify listening view of such changes.\")), mdx(\"div\", {\n    id: \"Lesson19DownloadManager7-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In each case, we update the state with the appropriate value, but in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"finished\"), \"  one, we want to remove the entry because there is no need to keep it in there when the download is complete.\")), mdx(\"div\", {\n    id: \"Lesson19DownloadManager8-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"For the operation to start or be queued, we must add it to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"operationQueue\"), \" which will then determine if it can start straight away or be queued depending on the number of currently running operations.\")), mdx(\"div\", {\n    id: \"Lesson19DownloadManager9-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Last, we create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"abort\"), \" function that will cancel an operation based on its name. Again remember that I told you that we will use the operation name to do this.\")), mdx(\"div\", {\n    id: \"Lesson19Outro-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And this bring the lesson to a close.  If you find it hard to understand any of the code, Logging each step and go over the code multiple times will definitely help. See you in lesson #20 where we will finish working on the coredata service, and integrate the download service in views, happy coding!!!\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduct","title":"Introduct"},{"url":"#updating-the-coredataserviceprotocol","title":"Updating the CoredataServiceProtocol"},{"url":"#updating-the-previewcoredataservice","title":"Updating the PreviewCoredataService"},{"url":"#updating-the-coredataservice","title":"Updating the CoredataService"},{"url":"#continuing-with-the-downloadoperation","title":"Continuing with the DownloadOperation"},{"url":"#download-manager","title":"Download Manager"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey guys, welcome back to lesson #19. Our app is coming along which is awesome. In this lesson, we will make few coredata related updates before continuing with the download feature. Let's go!\"), mdx(\"h2\", null, \"Updating the CoredataServiceProtocol\"), mdx(\"p\", null, \"We will update the coredata service protocol before updating the service itself, so add that line in there.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-CoredataServiceProtocol\\n\\nimport Foundation\\n\\nprotocol CoredataServiceProtocol {\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool\\n    func fetchGenres() -> [GenreViewModel]\\n    // highlight-start\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool\\n    // highlight-end\\n}\\n\")), mdx(\"h2\", null, \"Updating the PreviewCoredataService\"), mdx(\"p\", null, \"We are also obliged to always update the preview coredata service whenever we change the protocol.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-PreviewCoredataService\\n\\nimport CoreData\\n\\nclass PreviewCoredataService: CoredataServiceProtocol {\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool {\\n\\n        return true\\n    }\\n\\n    func fetchGenres() -> [GenreViewModel] {\\n        return GenreViewModel.listPlaceholders\\n    }\\n\\n    // highlight-start\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool {\\n        return false\\n    }\\n    // highlight-end\\n\\n}\\n\")), mdx(\"h2\", null, \"Updating the CoredataService\"), mdx(\"p\", null, \"And finally, add update the coredata service. \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson16UpdateCoredataService1\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n        ...\\n\\n    // highlight-start\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool {\\n        return false\\n    }\\n    // highlight-end\\n\\n    private func saveContext() -> Bool {\\n        do {\\n            if context.hasChanges {\\n                try context.save()\\n            }\\n        } catch {\\n            let nsError = error as NSError\\n            fatalError(\\\"Unresolved error \\\\(nsError), \\\\(nsError.userInfo)\\\")\\n        }\\n\\n        return true\\n    }\\n}\\n\")), mdx(\"p\", null, \"We will return false for now, and go back to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DownloadOperation\"), \" class to finish with that class.\"), mdx(\"h2\", null, \"Continuing with the DownloadOperation\"), mdx(\"p\", null, \"Now that we have a method that will save the downloaded episode, let's now be done the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DownloadOperation\"), \"  class.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson16UpdateDownloadOperation1\\n// $$liquidcoderFilename-DownloadOperation\\n\\nimport Foundation\\n\\nclass DownloadOperation: BaseOperation {\\n\\n                ...\\n\\n    override func main() {\\n\\n                ...\\n\\n        environment.downloader.didFinish = { [weak self] data in\\n\\n            // highlight-start\\n            guard let self = self,\\n                  let audio = data else { return}\\n            // highlight-end\\n\\n            // highlight-start\\n            if self.environment.coredata.saveEpisode(self.episode, audio: audio) {\\n                self.state = .finished(true)\\n            } else {\\n                self.state = .finished(false)\\n            }\\n            // highlight-end\\n\\n        }\\n\\n        environment.downloader.progress = { [weak self] value in\\n\\n            // update progress\\n\\n        }\\n\\n    }\\n\\n    override func cancel() {\\n\\n    }\\n}\\n\")), mdx(\"p\", null, \"In \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"didFinish\"), \", we first check if the audio data is valid, otherwise we return.\"), mdx(\"p\", null, \"If we have a valid audio, we save the episode in coredata, then depending on whether the operation was successful, we set the state to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"finished\"), \" .\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson16UpdateDownloadOperation2\\n// $$liquidcoderFilename-DownloadOperation\\n\\nimport Foundation\\n\\nclass DownloadOperation: BaseOperation {\\n\\n        ...\\n\\n    override func main() {\\n\\n                ...\\n\\n        environment.downloader.progress = { [weak self] value in\\n\\n            // highlight-start\\n            self?.state = .executing(value)\\n            // highlight-end\\n\\n        }\\n\\n    }\\n\\n    override func cancel() {\\n        // highlight-start\\n        super.cancel()\\n        self.environment.downloader.cancel()\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"In \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"progress\"), \" closure, we update the state with the progress.\"), mdx(\"p\", null, \"And in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cancel\"), \", we first call \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cancel\"), \" from the super class, and cancel the download process in the Downloader service class.\"), mdx(\"h2\", null, \"Download Manager\"), mdx(\"p\", null, \"The downloader manager is the class that will, as its name implies, manage all of the download processes. It will act like the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Store\"), \" , but it will only have one job.\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Networking\")), \" folder, add a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"DownloadManager.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS, macOS and watchOS-Extensions targets.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson16DownloadManager2\\n// $$liquidcoderFilename-DownloadManager\\n\\nimport SwiftUI\\n\\nclass DownloadManager: ObservableObject {\\n\\n    // highlight-start\\n    enum State: Equatable {\\n        case idle\\n        case pending\\n        case downloading(progress: CGFloat)\\n        case done(success: Bool)\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"p\", null, \"We create yet another \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"State\"), \" enum that will used be by views, we have added an extra case, the pending one, that will be active when a particular operation is in pending state meaning that when the operation order in the queue exceed the maximum count of running operations .\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson16DownloadManager3\\n// $$liquidcoderFilename-DownloadManager\\n\\nimport SwiftUI\\n\\nclass DownloadManager: ObservableObject {\\n\\n    enum State: Equatable {\\n        case idle\\n        case pending\\n        case downloading(progress: CGFloat)\\n        case done(success: Bool)\\n    }\\n\\n    // highlight-start\\n    @Published var state: State = .idle\\n    @Published var downloadings: [String: State] = [:]\\n    // highlight-end\\n}\\n\")), mdx(\"p\", null, \"We then declare a state property, as well as a downloadings dictionary property. Both having the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"@Published\"), \" property wrapper meaning that they will be observed by other views in the app.\"), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"downloadings\"), \" dictionary will accept strings as keys and states as values. The keys will operations.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson16DownloadManager4\\n// $$liquidcoderFilename-DownloadManager\\n\\nimport SwiftUI\\n\\nclass DownloadManager: ObservableObject {\\n\\n    enum State: Equatable {\\n        case idle\\n        case pending\\n        case downloading(progress: CGFloat)\\n        case done(success: Bool)\\n    }\\n\\n    @Published var state: State = .idle\\n    @Published var downloadings: [String: State] = [:]\\n\\n    // highlight-start\\n    private var operationQueue = OperationQueue()\\n        private var environment: AppEnvironment\\n    // highlight-end\\n}\\n\")), mdx(\"p\", null, \"Remember that I said operations are like boxes containing pieces of tasks to be executed, well the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"operationQueue\"), \" will be their coordinator. \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson16DownloadManager5\\n// $$liquidcoderFilename-DownloadManager\\n\\nimport SwiftUI\\n\\nclass DownloadManager: ObservableObject {\\n\\n    enum State: Equatable {\\n        case idle\\n        case pending\\n        case downloading(progress: CGFloat)\\n        case done(success: Bool)\\n    }\\n\\n    @Published var state: State = .idle\\n    @Published var downloadings: [String: State] = [:]\\n\\n    private var operationQueue = OperationQueue()\\n\\n        private var environment: AppEnvironment\\n\\n    // highlight-start\\n    init(environment: AppEnvironment = AppEnvironment()) {\\n        operationQueue.maxConcurrentOperationCount = 1\\n        operationQueue.qualityOfService = .background\\n\\n        self.environment = environment\\n    }\\n    // highlight-end\\n\\n        // highlight-start\\n    func download(_ episode: EpisodeViewModel) {\\n\\n    }\\n    // highlight-end\\n\\n}\\n\")), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"initialiser\"), \", we set the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"maxConcurrentOperationCount\"), \" to 1 to limit only 1 download process at a time. This will be dynamic based on the type of connexion we are currently using.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-DownloadManager\\n\\nimport SwiftUI\\n\\nclass DownloadManager: ObservableObject {\\n\\n        init(environment: AppEnvironment = AppEnvironment()) {\\n        operationQueue.maxConcurrentOperationCount = 1\\n        operationQueue.qualityOfService = .background\\n\\n        self.environment = environment\\n    }\\n\\n        ...\\n\\n    func download(_ episode: EpisodeViewModel) {\\n\\n        // highlight-start\\n        self.downloadings[episode.title] = .pending\\n        // highlight-end\\n\\n    }\\n}\\n\")), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"download\"), \", we add the episode that is about to be downloaded to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"downloadings\"), \" dictionary, setting its state to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"pending\"), \".\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-DownloadManager\\n\\nimport SwiftUI\\n\\nclass DownloadManager: ObservableObject {\\n\\n        ...\\n\\n    func download(_ episode: EpisodeViewModel) {\\n\\n        self.downloadings[episode.title] = .pending\\n\\n        let operation = DownloadOperation(episode: episode, environment: environment)\\n\\n        // highlight-start\\n        operation.onStateChange = { [weak self] state in\\n            switch state {\\n            case .idle:\\n                break\\n            case.executing(let progress):\\n                break\\n            case .finished(let success):\\n                break\\n            }\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"Remember the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onStateChange\"), \" closure? It will be updated whenever the state changes, and in turn it we will notify listening view of such changes.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-DownloadManager\\n\\nimport SwiftUI\\n\\nclass DownloadManager: ObservableObject {\\n\\n        ...\\n\\n    func download(_ episode: EpisodeViewModel) {\\n\\n        self.downloadings[episode.title] = .pending\\n\\n        let operation = DownloadOperation(episode: episode, environment: environment)\\n\\n        operation.onStateChange = { [weak self] state in\\n            switch state {\\n            case .idle:\\n                // highlight-start\\n                self?.downloadings[episode.title] = .pending\\n                // highlight-end\\n            case.executing(let progress):\\n                // highlight-start\\n                DispatchQueue.main.async {\\n                    self?.downloadings[episode.title] = .downloading(progress: progress)\\n                }\\n                // highlight-end\\n            case .finished(let success):\\n                // highlight-start\\n                DispatchQueue.main.async {\\n                    self?.state = .done(success: success)\\n                    self?.downloadings.removeValue(forKey: episode.title)\\n                }\\n                // highlight-end\\n            }\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"In each case, we update the state with the appropriate value, but in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"finished\"), \"  one, we want to remove the entry because there is no need to keep it in there when the download is complete.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson16DownloadManager10\\n// $$liquidcoderFilename-DownloadManager\\n\\nimport SwiftUI\\n\\nclass DownloadManager: ObservableObject {\\n\\n        ...\\n\\n    func download(_ episode: EpisodeViewModel) {\\n\\n        self.downloadings[episode.title] = .pending\\n\\n        let operation = DownloadOperation(episode: episode, environment: environment)\\n\\n        operation.onStateChange = { [weak self] state in\\n            switch state {\\n            case .idle:\\n                self?.downloadings[episode.title] = .pending\\n            case.executing(let progress):\\n                DispatchQueue.main.async {\\n                    self?.downloadings[episode.title] = .downloading(progress: progress)\\n                }\\n            case .finished(let success):\\n                DispatchQueue.main.async {\\n                    self?.state = .done(success: success)\\n                    self?.downloadings.removeValue(forKey: episode.title)\\n                }\\n            }\\n        }\\n\\n        // highlight-start\\n        operationQueue.addOperation(operation)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"For the operation to start or be queued, we must add it to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"operationQueue\"), \" which will then determine if it can start straight away or be queued depending on the number of currently running operations.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson16DownloadManager2\\n// $$liquidcoderFilename-DownloadManager\\n\\nimport SwiftUI\\n\\nclass DownloadManager: ObservableObject {\\n\\n        ...\\n\\n    func download(_ episode: EpisodeViewModel) {\\n\\n        self.downloadings[episode.title] = .pending\\n\\n        let operation = DownloadOperation(episode: episode, environment: environment)\\n\\n        operation.onStateChange = { [weak self] state in\\n            switch state {\\n            case .idle:\\n                self?.downloadings[episode.title] = .pending\\n            case.executing(let progress):\\n                DispatchQueue.main.async {\\n                    self?.downloadings[episode.title] = .downloading(progress: progress)\\n                }\\n            case .finished(let success):\\n                DispatchQueue.main.async {\\n                    self?.state = .done(success: success)\\n                    self?.downloadings.removeValue(forKey: episode.title)\\n                }\\n            }\\n        }\\n\\n        operationQueue.addOperation(operation)\\n    }\\n\\n    // highlight-start\\n    func abort(name: String) {\\n        if let operation = operationQueue.operations.first(where: {  $0.name == name }) {\\n            operation.cancel()\\n        }\\n    }\\n    // highlight-end\\n\\n}\\n\")), mdx(\"p\", null, \"Last, we create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"abort\"), \" function that will cancel an operation based on its name. Again remember that I told you that we will use the operation name to do this.\"), mdx(\"p\", null, \"And this bring the lesson to a close.  If you find it hard to understand any of the code, Logging each step and go over the code multiple times will definitely help. See you in lesson #20 where we will finish working on the coredata service, and integrate the download service in views, happy coding!!!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#updating-the-coredataserviceprotocol","title":"Updating the CoredataServiceProtocol"},{"url":"#updating-the-previewcoredataservice","title":"Updating the PreviewCoredataService"},{"url":"#updating-the-coredataservice","title":"Updating the CoredataService"},{"url":"#continuing-with-the-downloadoperation","title":"Continuing with the DownloadOperation"},{"url":"#download-manager","title":"Download Manager"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-03-15T09:11:58.414Z","isNewLayout":true,"previews":null,"slug":"lesson-20-downloading-episodes","title":"Lesson #20: Downloading Episodes","updatedDate":"2021-03-15T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZ7OhBXZLGOUaemEuxzSVAvpU6r8u4U91BX0","type":"zip","id":"0d784a1c-3845-555a-926c-63773cea8614","name":"Source code"}],"allChapters":null,"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateCoredataService0\\n// $$liquidcoderFilename-CoredataServiceProtocol\\n\\nimport Foundation\\n\\nprotocol CoredataServiceProtocol {\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool\\n    func fetchGenres() -> [GenreViewModel]\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool\\n    // highlight-start\\n    func fetchEpisode(_ episode: EpisodeViewModel) -> EpisodeManagedObject?\\n    func fetchPodcast(_ id: Int) -> PodcastManagedObject?\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdatePreviewCoredataService1\\n// $$liquidcoderFilename-PreviewCoredataService\\n\\nimport CoreData\\n\\nclass PreviewCoredataService: CoredataServiceProtocol {\\n\\n    // highlight-start\\n    func fetchEpisode(_ episode: EpisodeViewModel) -> EpisodeManagedObject? {\\n        return nil\\n    }\\n\\n    func fetchPodcast(_ id: Int) -> PodcastManagedObject? {\\n\\n    }\\n    // highlight-end\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool {\\n\\n        return true\\n    }\\n\\n    func fetchGenres() -> [GenreViewModel] {\\n        return GenreViewModel.listPlaceholders\\n    }\\n\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool {\\n        return false\\n    }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateCoredataService1\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n    private var context: NSManagedObjectContext {\\n        return PersistenceController.shared.container.viewContext\\n    }\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool {   ... }\\n\\n    func fetchGenres() -> [GenreViewModel] { ... }\\n\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool {\\n        return false\\n    }\\n\\n        // highlight-start\\n    func fetchEpisode(_ episode: EpisodeViewModel) -> EpisodeManagedObject? {\\n\\n    }\\n\\n    func fetchPodcast(_ id: Int) -> PodcastManagedObject? {\\n\\n    }\\n    // highlight-end    \\n\\n    // highlight-start\\n    private func createManagedEpisode(_ episode: EpisodeViewModel, audioData: Data) -> EpisodeManagedObject {\\n        let managedEpisode = EpisodeManagedObject(context: context)\\n        managedEpisode.id = episode.id\\n        managedEpisode.title = episode.title\\n        managedEpisode.createdAt = Date()\\n        managedEpisode.descr = episode.description\\n        managedEpisode.duration = episode.audioLength\\n        managedEpisode.releaseDate = episode.date.toDate()\\n        managedEpisode.size = episode.audioSize\\n        managedEpisode.sourceAudioUrl = episode.audioUrl\\n        managedEpisode.audio = audioData\\n        return managedEpisode\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    private func createManagedPodcast(_ podcast: PodcastViewModel) -> PodcastManagedObject {\\n\\n        let managedPodcast = PodcastManagedObject(context: context)\\n        managedPodcast.title = podcast.title\\n        managedPodcast.author = podcast.author\\n        managedPodcast.createdAt = Date()\\n        managedPodcast.country = podcast.country\\n        managedPodcast.descr = podcast.description\\n        managedPodcast.explicitContent = podcast.explicitContent\\n        managedPodcast.feedUrl = podcast.feedUrl\\n                managedPodcast.releaseDate = podcast.date.toDate()\\n        managedPodcast.language = podcast.language\\n        managedPodcast.publisher = podcast.publisher\\n        managedPodcast.id = Int64(podcast.id)\\n        managedPodcast.imageUrl = podcast.imageUrl\\n        managedPodcast.thumbnailUrl = podcast.thumbnailUrl\\n        managedPodcast.genres = podcast.genres.joined(separator: \\\",\\\")\\n        return managedPodcast\\n    }\\n    // highlight-end\\n\\n    private func saveContext() -> Bool {\\n        do {\\n            if context.hasChanges {\\n                try context.save()\\n            }\\n        } catch {\\n            let nsError = error as NSError\\n            fatalError(\\\"Unresolved error \\\\(nsError), \\\\(nsError.userInfo)\\\")\\n        }\\n\\n        return true\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateCoredataService2\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n    private var context: NSManagedObjectContext {\\n        return PersistenceController.shared.container.viewContext\\n    }\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool { ... }\\n\\n    func fetchGenres() -> [GenreViewModel] { ... }\\n\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool {\\n        return false\\n    }\\n\\n    func fetchEpisode(_ episode: EpisodeViewModel) -> EpisodeManagedObject? {\\n        // highlight-start\\n        let request: NSFetchRequest<EpisodeManagedObject> = EpisodeManagedObject.fetchRequest()\\n        request.predicate = NSPredicate(format: \\\"title = %@\\\", episode.title)\\n\\n        do {\\n            let results = try context.fetch(request)\\n            return results.first\\n\\n        } catch let error {\\n            print(error)\\n        }\\n\\n        return nil\\n        // highlight-end\\n    }\\n\\n    func fetchPodcast(_ id: Int) -> PodcastManagedObject? {\\n    // highlight-start\\n        let request: NSFetchRequest<PodcastManagedObject> = PodcastManagedObject.fetchRequest()\\n        request.predicate = NSPredicate(format: \\\"id = %@\\\", String(id))\\n\\n        do {\\n            let results = try context.fetch(request)\\n            return results.first\\n\\n        } catch let error {\\n            print(error)\\n        }\\n\\n        return nil\\n    // highlight-end\\n    }\\n\\n    ...\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateCoredataService3\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n    private var context: NSManagedObjectContext {\\n        return PersistenceController.shared.container.viewContext\\n    }\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool { ... }\\n\\n    func fetchGenres() -> [GenreViewModel] { ... }\\n\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool {\\n\\n        do {\\n\\n            // highlight-start\\n            let managedEpisode = createManagedEpisode(episode, audioData: audio)\\n            // highlight-end\\n\\n            // highlight-start\\n            if let managedPodcast = fetchPodcast(episode.podcast.id) {\\n                managedPodcast.addToEpisodes(managedEpisode)\\n            }\\n            // highlight-end\\n\\n        } catch let error {\\n            Log.error(error)\\n            return false\\n        }\\n        return false\\n\\n    }\\n\\n        func fetchEpisode(_ episode: EpisodeViewModel) -> EpisodeManagedObject? {\\n        let request: NSFetchRequest<EpisodeManagedObject> = EpisodeManagedObject.fetchRequest()\\n        request.predicate = NSPredicate(format: \\\"title = %@\\\", episode.title)\\n\\n        do {\\n            let results = try context.fetch(request)\\n            return results.first\\n\\n        } catch let error {\\n            print(error)\\n        }\\n\\n        return nil\\n    }\\n\\n    func fetchPodcast(_ id: Int) -> PodcastManagedObject? {\\n        let request: NSFetchRequest<PodcastManagedObject> = PodcastManagedObject.fetchRequest()\\n        request.predicate = NSPredicate(format: \\\"id = %@\\\", String(id))\\n\\n        do {\\n            let results = try context.fetch(request)\\n            return results.first\\n\\n        } catch let error {\\n            print(error)\\n        }\\n\\n        return nil\\n    }\\n\\n        ...\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateCoredataService4\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n    private var context: NSManagedObjectContext {\\n        return PersistenceController.shared.container.viewContext\\n    }\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool { ... }\\n\\n    func fetchGenres() -> [GenreViewModel] { ... }\\n\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool {\\n\\n        do {\\n\\n            let managedEpisode = createManagedEpisode(episode, audioData: audio)\\n\\n            if let managedPodcast = fetchPodcast(episode.podcast.id) {\\n                managedPodcast.addToEpisodes(managedEpisode)\\n                // highlight-start\\n            } else {\\n                let podcast = episode.podcast\\n                let managedPodcast = createManagedPodcast(podcast)\\n                managedPodcast.addToEpisodes(managedEpisode)\\n\\n                if let imgUrl = URL(string: podcast.imageUrl) {\\n                    managedPodcast.image = try Data(contentsOf: imgUrl)\\n                }\\n\\n                if let thumbUrl = URL(string: podcast.thumbnailUrl) {\\n                    managedPodcast.thumbnail = try Data(contentsOf: thumbUrl)\\n                }\\n            }\\n            // highlight-end\\n\\n        } catch let error {\\n            Log.error(error)\\n            return false\\n        }\\n\\n        // highlight-start\\n        return saveContext()\\n        // highlight-end\\n\\n   }\\n\\n    func fetchEpisode(_ episode: EpisodeViewModel) -> EpisodeManagedObject? {\\n         ...\\n    }\\n\\n    func fetchPodcast(_ id: Int) -> PodcastManagedObject? {\\n         ...\\n    }\\n\\n    private func createManagedEpisode(_ episode: EpisodeViewModel, audioData: Data) -> EpisodeManagedObject {\\n        ...    \\n    }\\n\\n    private func createManagedPodcast(_ podcast: PodcastViewModel) -> PodcastManagedObject {\\n        ...\\n    }\\n\\n    private func saveContext() -> Bool {\\n        do {\\n            if context.hasChanges {\\n                try context.save()\\n            }\\n        } catch {\\n            let nsError = error as NSError\\n            fatalError(\\\"Unresolved error \\\\(nsError), \\\\(nsError.userInfo)\\\")\\n        }\\n\\n        return true\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateEpisodeViewHeader1\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n    var episode: EpisodeViewModel\\n    var downloadEpisode = {}\\n\\n    // highlight-start\\n    @State private var state = DownloadManager.State.idle\\n    // highlight-start\\n\\n    var body: some View {\\n        HStack {\\n            Text(episode.date)\\n            Spacer()\\n            Button(action: downloadEpisode, label: {\\n                Image(systemName: \\\"icloud.and.arrow.down.fill\\\")\\n                    .resizable()\\n                    .scaledToFit()\\n                    .frame(width: 20, height: 20)\\n                    .foregroundColor(.border)\\n            })\\n        }\\n    }\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateEpisodeViewHeader2\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n    var episode: EpisodeViewModel\\n    var downloadEpisode = {}\\n\\n    @State private var state = DownloadManager.State.idle\\n\\n    var body: some View {\\n        HStack {\\n            Text(episode.date)\\n            Spacer()\\n            // highlight-start\\n            createDownloadButton()\\n            // highlight-end\\n        }\\n    }\\n\\n    // highlight-start\\n    @ViewBuilder\\n    private func createDownloadButton() -> some View {\\n        switch state {\\n        case .idle:\\n\\n        case .pending:\\n\\n        case .downloading(let progress):\\n\\n        case .done (let success):\\n\\n        }\\n    }\\n    // highlight-end\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateEpisodeViewHeader3\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n    var episode: EpisodeViewModel\\n\\n    @State private var state = DownloadManager.State.idle\\n\\n    // highlight-start\\n    private var downloadButton: some View {\\n        Button(action: downloadEpisode, label: {\\n            Image(systemName: \\\"icloud.and.arrow.down.fill\\\")\\n                .resizable()\\n                .scaledToFit()\\n                                .frame(width: 20, height: 20)\\n                .foregroundColor(.border)\\n        })\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        HStack {\\n            Text(episode.date)\\n            Spacer()\\n            createDownloadButton()\\n        }\\n    }\\n\\n    @ViewBuilder\\n    private func createDownloadButton() -> some View {\\n        switch state {\\n        case .idle:\\n        // highlight-start\\n            downloadButton\\n        // highlight-end\\n        case .pending:\\n\\n        case .downloading(let progress):\\n\\n          case .done (let success):\\n            if success {\\n\\n            } else {\\n                // highlight-start\\n                downloadButton\\n                // highlight-end\\n            }\\n        }\\n    }\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateEpisodeViewHeader4\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n    var episode: EpisodeViewModel\\n    var downloadEpisode = {}\\n\\n    @State private var state = DownloadManager.State.idle\\n\\n        private var downloadButton: some View {\\n        Button(action: downloadEpisode, label: {\\n            Image(systemName: \\\"icloud.and.arrow.down.fill\\\")\\n                .resizable()\\n                .scaledToFit()\\n                .frame(width: 20, height: 20)\\n                .foregroundColor(.border)\\n        })\\n    }\\n\\n    // highlight-start\\n    private var pendingView: some View {\\n        Image(systemName: \\\"clock.fill\\\")\\n            .resizable()\\n            .scaledToFit()\\n            .frame(width: 20, height: 20)\\n            .foregroundColor(.border)\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        HStack {\\n            Text(episode.date)\\n            Spacer()\\n            createDownloadButton()\\n        }\\n    }\\n\\n    @ViewBuilder\\n    private func createDownloadButton() -> some View {\\n        switch state {\\n        case .idle:\\n            downloadButton\\n        case .pending:\\n            // highlight-start\\n            pendingView\\n        // highlight-end\\n        case .downloading(let progress):\\n\\n        case .done (let success):\\n            if success {\\n\\n            } else {\\n                downloadButton\\n            }\\n        }\\n    }\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateEpisodeViewHeader5\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n    var episode: EpisodeViewModel\\n    var downloadEpisode = {}\\n\\n    @State private var state = DownloadManager.State.idle\\n\\n    private var downloadButton: some View { ... }\\n\\n    private var pendingView: some View { ... }\\n\\n    var body: some View {\\n        HStack {\\n            Text(episode.date)\\n            Spacer()\\n            createDownloadButton()\\n        }\\n    }\\n\\n    @ViewBuilder\\n    private func createDownloadButton() -> some View {\\n        switch state {\\n        case .idle:\\n            downloadButton\\n        case .pending:\\n            pendingView\\n        case .downloading(let progress):\\n            // highlight-start\\n            createProgressView(progress)\\n            // highlight-end\\n        case .done (let success):\\n            if success {\\n\\n            } else {\\n                downloadButton\\n            }\\n        }\\n    }\\n\\n    // highlight-start\\n    private func createProgressView(_ progress: CGFloat) -> some View {\\n\\n    }\\n    // highlight-end  \\n\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateEpisodeViewHeader6\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n    var episode: EpisodeViewModel\\n\\n    @State private var state = DownloadManager.State.idle\\n\\n    // highlight-start\\n    @EnvironmentObject private var downloadManager: DownloadManager\\n    // highlight-end\\n\\n    private var downloadButton: some View { ... }\\n\\n    private var pendingView: some View { ... }\\n\\n    var body: some View {\\n        HStack {\\n            Text(episode.date)\\n            Spacer()\\n            createDownloadButton()\\n        }\\n    }\\n\\n    @ViewBuilder\\n    private func createDownloadButton() -> some View { ... }\\n\\n    // highlight-start\\n    private func createProgressView(_ progress: CGFloat) -> some View {\\n        Button(action: abort, label: {\\n            Text(\\\"\\\\(progress)\\\")\\n        })\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    private func abort() {\\n        downloadManager.abort(name: episode.title)\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    private func downloadEpisode() {\\n        downloadManager.download(episode)\\n    }\\n     // highlight-end\\n\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateEpisodeViewHeader7\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n    var episode: EpisodeViewModel\\n\\n    @State private var state = DownloadManager.State.idle\\n\\n    @EnvironmentObject private var downloadManager: DownloadManager\\n\\n    private var downloadButton: some View {\\n        Button(action: downloadEpisode, label: {\\n            Image(systemName: \\\"icloud.and.arrow.down.fill\\\")\\n                .resizable()\\n                .scaledToFit()\\n                .foregroundColor(.border)\\n        })\\n    }\\n\\n    private var pendingView: some View {\\n        Text(\\\"Pending...\\\")\\n            .font(.callout)\\n            .foregroundColor(.gray)\\n    }\\n\\n    // highlight-start\\n    private var savedImageView: some View {\\n        Image(systemName: \\\"icloud.fill\\\")\\n            .resizable()\\n            .scaledToFit()\\n            .frame(width: 20, height: 20)\\n            .foregroundColor(.accentColor)\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        HStack {\\n            Text(episode.date)\\n            Spacer()\\n            createDownloadButton()\\n        }\\n    }\\n\\n    @ViewBuilder\\n    private func createDownloadButton() -> some View {\\n        switch state {\\n        case .idle:\\n            downloadButton\\n        case .pending:\\n            pendingView\\n        case .downloading(let progress):\\n            progressView(progress)\\n        case .done (let success):\\n            if success {\\n                // highlight-start\\n                savedImageView\\n                // highlight-end\\n            } else {\\n                downloadButton\\n            }\\n        }\\n    }\\n\\n    private func createProgressView(_ progress: CGFloat) -> some View {\\n        Button(action: abort, label: {\\n            Text(\\\"\\\\(progress)\\\")\\n        })\\n    }\\n\\n    private func abort() {\\n        downloadManager.abort(name: episode.title)\\n    }\\n\\n    private func downloadEpisode() {\\n        downloadManager.download(episode)\\n    }\\n\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateDownloadManager1\\n// $$liquidcoderFilename-DownloadManager\\n\\nimport Foundation\\nimport SwiftUI\\n\\nclass DownloadManager: ObservableObject {\\n\\n    ...\\n\\n    // highlight-start\\n    func getState(for episode: EpisodeViewModel) -> State {\\n        if let state = downloadings[episode.title] {\\n            return state\\n        }\\n\\n        let episode = environment.coredata.fetchEpisode(episode)\\n        self.state = .done(success: episode != nil)\\n        return .done(success: episode != nil)\\n    }\\n    // highlight-end\\n\\n    ...\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateEpisodeViewHeader8\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n    var episode: EpisodeViewModel\\n\\n    @State private var state = DownloadManager.State.idle\\n\\n    @EnvironmentObject private var downloadManager: DownloadManager\\n\\n    private var downloadButton: some View { ... }\\n\\n    private var pendingView: some View { ... }\\n\\n    private var savedImageView: some View { ... }\\n\\n    var body: some View {\\n        HStack {\\n            Text(episode.date)\\n            Spacer()\\n            createDownloadButton()\\n\\n        // highlight-start\\n        }.onAppear {\\n            state = downloadManager.getState(for: episode)\\n        }\\n        // highlight-end\\n    }\\n\\n    @ViewBuilder\\n    private func createDownloadButton() -> some View {\\n        switch state {\\n        case .idle:\\n            downloadButton\\n        case .pending:\\n            pendingView\\n        case .downloading(let progress):\\n            progressView(progress)\\n        case .done (let success):\\n            if success {\\n                savedImageView\\n            } else {\\n                downloadButton\\n            }\\n        }\\n    }\\n\\n    private func createProgressView(_ progress: CGFloat) -> some View {\\n        Button(action: abort, label: {\\n            Text(\\\"\\\\(progress)\\\")\\n        })\\n    }\\n\\n    private func abort() {\\n        downloadManager.abort(name: episode.title)\\n    }\\n\\n    private func downloadEpisode() {\\n        downloadManager.download(episode)\\n    }\\n\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateEpisodeViewHeader9\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n    var episode: EpisodeViewModel\\n\\n    @State private var state = DownloadManager.State.idle\\n\\n    @EnvironmentObject private var downloadManager: DownloadManager\\n\\n    private var downloadButton: some View {\\n        Button(action: downloadEpisode, label: {\\n            Image(systemName: \\\"icloud.and.arrow.down.fill\\\")\\n                .resizable()\\n                .scaledToFit()\\n                .foregroundColor(.border)\\n        })\\n    }\\n\\n    private var pendingView: some View {\\n        Text(\\\"Pending...\\\")\\n            .font(.callout)\\n            .foregroundColor(.gray)\\n    }\\n\\n    private var savedImageView: some View {\\n        Image(systemName: \\\"icloud.fill\\\")\\n            .resizable()\\n            .scaledToFit()\\n            .frame(width: 20, height: 20)\\n            .foregroundColor(.accentColor)\\n    }\\n\\n    var body: some View {\\n        HStack {\\n            Text(episode.date)\\n            Spacer()\\n            createDownloadButton()\\n\\n        }.onAppear {\\n            state = downloadManager.getState(for: episode)\\n            // highlight-start\\n        }.onChange(of: downloadManager.downloadings, perform: { downloadings in\\n            if let state = downloadings[episode.title] {\\n                self.state = state\\n            } else {\\n                self.state =  downloadManager.getState(for: episode)\\n            }\\n        })\\n        // highlight-end\\n    }\\n\\n    @ViewBuilder\\n    private func createDownloadButton() -> some View {\\n        switch state {\\n        case .idle:\\n            downloadButton\\n        case .pending:\\n            pendingView\\n        case .downloading(let progress):\\n            progressView(progress)\\n        case .done (let success):\\n            if success {\\n                savedImageView\\n            } else {\\n                downloadButton\\n            }\\n        }\\n    }\\n\\n    private func createProgressView(_ progress: CGFloat) -> some View {\\n        Button(action: abort, label: {\\n            Text(\\\"\\\\(progress)\\\")\\n        })\\n    }\\n\\n    private func abort() {\\n        downloadManager.abort(name: episode.title)\\n    }\\n\\n    private func downloadEpisode() {\\n        downloadManager.download(episode)\\n    }\\n\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n            // highlight-start\\n        }.environmentObject(DownloadManager())\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateStreamCastApp1\\n// $$liquidcoderFilename-StreamCastApp\\n\\nimport SwiftUI\\n\\n@main\\nstruct StreamCastApp: App {\\n    let persistenceController = PersistenceController.shared\\n    @ObservedObject private var store = Store()\\n    // highlight-start\\n    @ObservedObject private var downloadManager = DownloadManager()\\n    // highlight-end\\n\\n    var body: some Scene {\\n        WindowGroup {\\n            ContentView()\\n                .environmentObject(store)\\n                // highlight-start\\n                .environmentObject(downloadManager)\\n                // highlight-end\\n                .environment(\\\\.managedObjectContext, persistenceController.container.viewContext)\\n        }\\n    }\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson20Intro-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey, welcome to lesson #20. we made few coredata related updates before continuing with the download feature. In this one, we will finish this feature by updating the coredata service and the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeViewHeader\"))), mdx(\"div\", {\n    id: \"Lesson20UpdateCoredataService0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the coredata service protocol\"), mdx(\"p\", null, \"Let's add 2 new methods to the coredata service protocol. We still have to implement the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"saveEpisode\"), \" one as well.\")), mdx(\"div\", {\n    id: \"Lesson20UpdatePreviewCoredataService1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the preview coredata service\"), mdx(\"p\", null, \"As always we also need to update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PreviewCoredataService\"), \".\")), mdx(\"div\", {\n    id: \"Lesson20UpdateCoredataService1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the coredata service\"), mdx(\"p\", null, \"And finally, let's do the actual work in the real service itself.\"), mdx(\"hr\", null), mdx(\"p\", null, \"First we bring in the 2 protocol methods, then we create 2 helper methods that create an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeManagedObject\"), \" and a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastManagedObject\"), \" given their respective view models\")), mdx(\"div\", {\n    id: \"Lesson20UpdateCoredataService2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next, we fetch the episode managed object using its title and we use the id to fetch the podcast.\")), mdx(\"div\", {\n    id: \"Lesson20UpdateCoredataService3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Saving an episode is not as straight forward as we've done with other managed object, but it's not hard as well.\"), mdx(\"hr\", null), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We first create the episode using the helper function we've created earlier. \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then we check whether we have an existing podcast for this episode, if so, we add the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"managedEpisode\"), \" to its set and we are done. We just need to save the context to persist the change.\"))), mdx(\"div\", {\n    id: \"Lesson20UpdateCoredataService4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"If we don't have an existing podcast, well, we need to create a new one using the podcast for the provided episode.\"), mdx(\"p\", null, \"Having the new \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"managedPodcast\"), \", we just need to add the episode in its set, update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"image\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"thumbnail\"), \", and save the context to persist the changes we've made so far.\")), mdx(\"div\", {\n    id: \"Lesson20UpdateEpisodeViewHeader1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Update the EpisodeViewHeader\"), mdx(\"p\", null, \"I told you earlier that the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeViewHeader\"), \" will have a complex logic in it, so let's now implement that.\"), mdx(\"hr\", null), mdx(\"p\", null, \"Just like we did in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailScreen\"), \", we need a local state to track the actual download status  for a given episode.\")), mdx(\"div\", {\n    id: \"Lesson20UpdateEpisodeViewHeader2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We then listen to that local \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createDownloadButton\"), \" function in order to return an appropriate view. \")), mdx(\"div\", {\n    id: \"Lesson20UpdateEpisodeViewHeader3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We first create the download button that will be displayed when the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" is \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"idle\"), \" or the download operation has failed which will allow the user to retry again.\")), mdx(\"div\", {\n    id: \"Lesson20UpdateEpisodeViewHeader4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next, we create the pending view that will be displayed when an episode is queue for download.\")), mdx(\"div\", {\n    id: \"Lesson20UpdateEpisodeViewHeader5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next, we create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"progressView\"), \" returned in the progress case block passing in the progress value.\"), mdx(\"hr\", null), mdx(\"p\", null, \"We will create the actual progress view later.\")), mdx(\"div\", {\n    id: \"Lesson20UpdateEpisodeViewHeader6-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"For now, let's just put this button as placeholder.\"), mdx(\"hr\", null), mdx(\"p\", null, \"We also create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"abort\"), \" method that we call in the progress button action, and replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"downloadEpisode\"), \" closure with that \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"downloadEpisode\"), \" function. You will need to remove the second parameter in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeView\"), \" .\")), mdx(\"div\", {\n    id: \"Lesson20UpdateEpisodeViewHeader7-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"savedImageView\"), \" will be displayed if the current episode has been successfully saved that why it's called in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"done\"), \" block when \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"success\"), \" is true.\")), mdx(\"div\", {\n    id: \"Lesson20UpdateDownloadManager1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the DownloadManager\"), mdx(\"p\", null, \"Add the following function in the download manager class that returns a state for a given episode.\"), mdx(\"hr\", null), mdx(\"p\", null, \"Here we first check if the current episode is being downloaded, and if so, we return the state, otherwise we check if it's already saved, and return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".done\"), \" with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"true\"), \" or \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"false\"), \" depending on whether the episode is saved or not.\")), mdx(\"div\", {\n    id: \"Lesson20UpdateEpisodeViewHeader8-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We want to update the local state whenever this view appears, so we get its state from the download manager using its episode view model.\")), mdx(\"div\", {\n    id: \"Lesson20UpdateEpisodeViewHeader9-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And finally, we want to listen to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"downloadings\"), \" changes, and update the state accordingly. \"), mdx(\"p\", null, \"So, we need to check if the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"downloadings\"), \" dictionary contains a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \"  for the current episode, then update the local state, otherwise we need to check if the episode is already save or not.\")), mdx(\"div\", {\n    id: \"Lesson20UpdateStreamCastApp1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the StreamCastApp\"), mdx(\"p\", null, \"In order to navigate to the episodes screen, you will need to add the following to the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"StreamCastApp.swift\"), \" file.\")), mdx(\"div\", {\n    id: \"Lesson20Outro-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And this bring the lesson #20 to close guys. See you in #21 where we will finish creating the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeViewHeader\"), \" and update \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailScreen\"), \". Happy coding!!!\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#updating-the-coredata-service-protocol","title":"Updating the coredata service protocol"},{"url":"#updating-the-preview-coredata-service","title":"Updating the preview coredata service"},{"url":"#updating-the-coredata-service","title":"Updating the coredata service"},{"url":"#update-the-episodeviewheader","title":"Update the EpisodeViewHeader"},{"url":"#updating-the-downloadmanager","title":"Updating the DownloadManager"},{"url":"#updating-the-streamcastapp","title":"Updating the StreamCastApp"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey, welcome to lesson #20. we made few coredata related updates before continuing with the download feature. In this one, we will finish this feature by updating the coredata service and the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeViewHeader\")), mdx(\"h2\", null, \"Updating the coredata service protocol\"), mdx(\"p\", null, \"Let's add 2 new methods to the coredata service protocol. We still have to implement the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"saveEpisode\"), \" one as well.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateCoredataService0\\n// $$liquidcoderFilename-CoredataServiceProtocol\\n\\nimport Foundation\\n\\nprotocol CoredataServiceProtocol {\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool\\n    func fetchGenres() -> [GenreViewModel]\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool\\n    // highlight-start\\n    func fetchEpisode(_ episode: EpisodeViewModel) -> EpisodeManagedObject?\\n        func fetchPodcast(_ id: Int) -> PodcastManagedObject?\\n    // highlight-end\\n}\\n\")), mdx(\"h2\", null, \"Updating the preview coredata service\"), mdx(\"p\", null, \"As always we also need to update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PreviewCoredataService\"), \".\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdatePreviewCoredataService1\\n// $$liquidcoderFilename-PreviewCoredataService\\n\\nimport CoreData\\n\\nclass PreviewCoredataService: CoredataServiceProtocol {\\n\\n    // highlight-start\\n    func fetchEpisode(_ episode: EpisodeViewModel) -> EpisodeManagedObject? {\\n        return nil\\n    }\\n\\n        func fetchPodcast(_ id: Int) -> PodcastManagedObject? {\\n\\n        }\\n    // highlight-end\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool {\\n\\n        return true\\n    }\\n\\n    func fetchGenres() -> [GenreViewModel] {\\n        return GenreViewModel.listPlaceholders\\n    }\\n\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool {\\n        return false\\n    }\\n\\n}\\n\")), mdx(\"h2\", null, \"Updating the coredata service\"), mdx(\"p\", null, \"And finally, let's do the actual work in the real service itself.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateCoredataService1\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n    private var context: NSManagedObjectContext {\\n        return PersistenceController.shared.container.viewContext\\n    }\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool {   ... }\\n\\n    func fetchGenres() -> [GenreViewModel] { ... }\\n\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool {\\n        return false\\n    }\\n\\n// highlight-start\\n    func fetchEpisode(_ episode: EpisodeViewModel) -> EpisodeManagedObject? {\\n\\n    }\\n\\n        func fetchPodcast(_ id: Int) -> PodcastManagedObject? {\\n\\n        }\\n    // highlight-end    \\n\\n    // highlight-start\\n    private func createManagedEpisode(_ episode: EpisodeViewModel, audioData: Data) -> EpisodeManagedObject {\\n        let managedEpisode = EpisodeManagedObject(context: context)\\n        managedEpisode.id = episode.id\\n        managedEpisode.title = episode.title\\n        managedEpisode.createdAt = Date()\\n        managedEpisode.descr = episode.description\\n        managedEpisode.duration = episode.audioLength\\n        managedEpisode.releaseDate = episode.date.toDate()\\n        managedEpisode.size = episode.audioSize\\n        managedEpisode.sourceAudioUrl = episode.audioUrl\\n        managedEpisode.audio = audioData\\n        return managedEpisode\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    private func createManagedPodcast(_ podcast: PodcastViewModel) -> PodcastManagedObject {\\n\\n        let managedPodcast = PodcastManagedObject(context: context)\\n        managedPodcast.title = podcast.title\\n        managedPodcast.author = podcast.author\\n        managedPodcast.createdAt = Date()\\n        managedPodcast.country = podcast.country\\n        managedPodcast.descr = podcast.description\\n        managedPodcast.explicitContent = podcast.explicitContent\\n        managedPodcast.feedUrl = podcast.feedUrl\\n                managedPodcast.releaseDate = podcast.date.toDate()\\n        managedPodcast.language = podcast.language\\n        managedPodcast.publisher = podcast.publisher\\n        managedPodcast.id = Int64(podcast.id)\\n        managedPodcast.imageUrl = podcast.imageUrl\\n        managedPodcast.thumbnailUrl = podcast.thumbnailUrl\\n        managedPodcast.genres = podcast.genres.joined(separator: \\\",\\\")\\n        return managedPodcast\\n    }\\n    // highlight-end\\n\\n    private func saveContext() -> Bool {\\n        do {\\n            if context.hasChanges {\\n                try context.save()\\n            }\\n        } catch {\\n            let nsError = error as NSError\\n            fatalError(\\\"Unresolved error \\\\(nsError), \\\\(nsError.userInfo)\\\")\\n        }\\n\\n        return true\\n    }\\n}\\n\")), mdx(\"p\", null, \"First we bring in the 2 protocol methods, then we create 2 helper methods that create an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeManagedObject\"), \" and a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastManagedObject\"), \" given their respective view models\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateCoredataService2\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n    private var context: NSManagedObjectContext {\\n        return PersistenceController.shared.container.viewContext\\n    }\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool { ... }\\n\\n    func fetchGenres() -> [GenreViewModel] { ... }\\n\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool {\\n        return false\\n    }\\n\\n        func fetchEpisode(_ episode: EpisodeViewModel) -> EpisodeManagedObject? {\\n        // highlight-start\\n        let request: NSFetchRequest<EpisodeManagedObject> = EpisodeManagedObject.fetchRequest()\\n        request.predicate = NSPredicate(format: \\\"title = %@\\\", episode.title)\\n\\n        do {\\n            let results = try context.fetch(request)\\n            return results.first\\n\\n        } catch let error {\\n            print(error)\\n        }\\n\\n        return nil\\n        // highlight-end\\n    }\\n\\n    func fetchPodcast(_ id: Int) -> PodcastManagedObject? {\\n    // highlight-start\\n        let request: NSFetchRequest<PodcastManagedObject> = PodcastManagedObject.fetchRequest()\\n        request.predicate = NSPredicate(format: \\\"id = %@\\\", String(id))\\n\\n        do {\\n            let results = try context.fetch(request)\\n            return results.first\\n\\n        } catch let error {\\n            print(error)\\n        }\\n\\n        return nil\\n    // highlight-end\\n    }\\n\\n    ...\\n}\\n\")), mdx(\"p\", null, \"Next, we fetch the episode managed object using its title and we use the id to fetch the podcast.\"), mdx(\"p\", null, \"Saving an episode is not as straight forward as we've done with other managed object, but it's not hard as well.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateCoredataService3\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n    private var context: NSManagedObjectContext {\\n        return PersistenceController.shared.container.viewContext\\n    }\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool { ... }\\n\\n    func fetchGenres() -> [GenreViewModel] { ... }\\n\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool {\\n\\n        do {\\n\\n            // highlight-start\\n            let managedEpisode = createManagedEpisode(episode, audioData: audio)\\n            // highlight-end\\n\\n            // highlight-start\\n            if let managedPodcast = fetchPodcast(episode.podcast.id) {\\n                managedPodcast.addToEpisodes(managedEpisode)\\n            }\\n            // highlight-end\\n\\n        } catch let error {\\n            Log.error(error)\\n            return false\\n        }\\n        return false\\n\\n    }\\n\\n        func fetchEpisode(_ episode: EpisodeViewModel) -> EpisodeManagedObject? {\\n        let request: NSFetchRequest<EpisodeManagedObject> = EpisodeManagedObject.fetchRequest()\\n        request.predicate = NSPredicate(format: \\\"title = %@\\\", episode.title)\\n\\n        do {\\n            let results = try context.fetch(request)\\n            return results.first\\n\\n        } catch let error {\\n            print(error)\\n        }\\n\\n        return nil\\n    }\\n\\n    func fetchPodcast(_ id: Int) -> PodcastManagedObject? {\\n        let request: NSFetchRequest<PodcastManagedObject> = PodcastManagedObject.fetchRequest()\\n        request.predicate = NSPredicate(format: \\\"id = %@\\\", String(id))\\n\\n        do {\\n            let results = try context.fetch(request)\\n            return results.first\\n\\n        } catch let error {\\n            print(error)\\n        }\\n\\n        return nil\\n    }\\n\\n        ...\\n}\\n\")), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We first create the episode using the helper function we've created earlier. \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then we check whether we have an existing podcast for this episode, if so, we add the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"managedEpisode\"), \" to its set and we are done. We just need to save the context to persist the change.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateCoredataService4\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n    private var context: NSManagedObjectContext {\\n        return PersistenceController.shared.container.viewContext\\n    }\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool { ... }\\n\\n    func fetchGenres() -> [GenreViewModel] { ... }\\n\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool {\\n\\n        do {\\n\\n            let managedEpisode = createManagedEpisode(episode, audioData: audio)\\n\\n            if let managedPodcast = fetchPodcast(episode.podcast.id) {\\n                managedPodcast.addToEpisodes(managedEpisode)\\n                // highlight-start\\n            } else {\\n                let podcast = episode.podcast\\n                let managedPodcast = createManagedPodcast(podcast)\\n                managedPodcast.addToEpisodes(managedEpisode)\\n\\n                if let imgUrl = URL(string: podcast.imageUrl) {\\n                    managedPodcast.image = try Data(contentsOf: imgUrl)\\n                }\\n\\n                if let thumbUrl = URL(string: podcast.thumbnailUrl) {\\n                    managedPodcast.thumbnail = try Data(contentsOf: thumbUrl)\\n                }\\n            }\\n            // highlight-end\\n\\n        } catch let error {\\n            Log.error(error)\\n            return false\\n        }\\n\\n        // highlight-start\\n        return saveContext()\\n        // highlight-end\\n\\n    }\\n\\n        func fetchEpisode(_ episode: EpisodeViewModel) -> EpisodeManagedObject? {\\n        ...\\n    }\\n\\n    func fetchPodcast(_ id: Int) -> PodcastManagedObject? {\\n        ...\\n    }\\n\\n    private func createManagedEpisode(_ episode: EpisodeViewModel, audioData: Data) -> EpisodeManagedObject {\\n        ...\\n    }\\n\\n    private func createManagedPodcast(_ podcast: PodcastViewModel) -> PodcastManagedObject {\\n        ...\\n    }\\n\\n    private func saveContext() -> Bool {\\n        do {\\n            if context.hasChanges {\\n                try context.save()\\n            }\\n        } catch {\\n            let nsError = error as NSError\\n            fatalError(\\\"Unresolved error \\\\(nsError), \\\\(nsError.userInfo)\\\")\\n        }\\n\\n        return true\\n    }\\n}\\n\")), mdx(\"p\", null, \"If we don't have an existing podcast, well, we need to create a new one using the podcast for the provided episode.\"), mdx(\"p\", null, \"Having the new \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"managedPodcast\"), \", we just need to add the episode in its set, update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"image\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"thumbnail\"), \", and save the context to persist the changes we've made so far.\"), mdx(\"h2\", null, \"Update the EpisodeViewHeader\"), mdx(\"p\", null, \"I told you earlier that the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeViewHeader\"), \" will have a complex logic in it, so let's now implement that.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateEpisodeViewHeader1\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n    var episode: EpisodeViewModel\\n    var downloadEpisode = {}\\n\\n    // highlight-start\\n    @State private var state = DownloadManager.State.idle\\n    // highlight-start\\n\\n    var body: some View {\\n        HStack {\\n            Text(episode.date)\\n            Spacer()\\n            Button(action: downloadEpisode, label: {\\n                Image(systemName: \\\"icloud.and.arrow.down.fill\\\")\\n                    .resizable()\\n                    .scaledToFit()\\n                    .frame(width: 20, height: 20)\\n                    .foregroundColor(.border)\\n            })\\n        }\\n    }\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"Just like we did in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailScreen\"), \", we need a local state to track the actual download status  for a given episode.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateEpisodeViewHeader2\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n    var episode: EpisodeViewModel\\n    var downloadEpisode = {}\\n\\n    @State private var state = DownloadManager.State.idle\\n\\n    var body: some View {\\n        HStack {\\n            Text(episode.date)\\n            Spacer()\\n            // highlight-start\\n            createDownloadButton()\\n            // highlight-end\\n        }\\n    }\\n\\n    // highlight-start\\n    @ViewBuilder\\n    private func createDownloadButton() -> some View {\\n        switch state {\\n        case .idle:\\n\\n        case .pending:\\n\\n        case .downloading(let progress):\\n\\n        case .done (let success):\\n\\n        }\\n    }\\n    // highlight-start\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"Just like we did in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailScreen\"), \", we need a local state to track the actual download status  for a given episode.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateEpisodeViewHeader3\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n    var episode: EpisodeViewModel\\n    var downloadEpisode = {}\\n\\n    @State private var state = DownloadManager.State.idle\\n\\n    // highlight-start\\n    private var downloadButton: some View {\\n        Button(action: downloadEpisode, label: {\\n            Image(systemName: \\\"icloud.and.arrow.down.fill\\\")\\n                .resizable()\\n                .scaledToFit()\\n                                .frame(width: 20, height: 20)\\n                .foregroundColor(.border)\\n        })\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        HStack {\\n            Text(episode.date)\\n            Spacer()\\n            createDownloadButton()\\n        }\\n    }\\n\\n    @ViewBuilder\\n    private func createDownloadButton() -> some View {\\n        switch state {\\n        case .idle:\\n        // highlight-start\\n        downloadButton\\n        // highlight-end\\n        case .pending:\\n\\n        case .downloading(let progress):\\n\\n        case .done (let success):\\n            if success {\\n\\n            } else {\\n                // highlight-start\\n                downloadButton\\n                // highlight-start\\n            }\\n        }\\n    }\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"We first create the download button that will be displayed when the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" is \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"idle\"), \" or the download operation has failed which will allow the user to retry again.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateEpisodeViewHeader4\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n    var episode: EpisodeViewModel\\n    var downloadEpisode = {}\\n\\n    @State private var state = DownloadManager.State.idle\\n\\n        private var downloadButton: some View {\\n        Button(action: downloadEpisode, label: {\\n            Image(systemName: \\\"icloud.and.arrow.down.fill\\\")\\n                .resizable()\\n                .scaledToFit()\\n                .frame(width: 20, height: 20)\\n                .foregroundColor(.border)\\n        })\\n    }\\n\\n    // highlight-start\\n    private var pendingView: some View {\\n        Image(systemName: \\\"clock.fill\\\")\\n            .resizable()\\n            .scaledToFit()\\n            .frame(width: 20, height: 20)\\n            .foregroundColor(.border)\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        HStack {\\n            Text(episode.date)\\n            Spacer()\\n            createDownloadButton()\\n        }\\n    }\\n\\n    @ViewBuilder\\n    private func createDownloadButton() -> some View {\\n        switch state {\\n        case .idle:\\n        downloadButton\\n        case .pending:\\n            // highlight-start\\n            pendingView\\n        // highlight-end\\n        case .downloading(let progress):\\n\\n        case .done (let success):\\n            if success {\\n\\n            } else {\\n                downloadButton\\n            }\\n        }\\n    }\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"Next, we create the pending view that will be displayed when an episode is queue for download.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateEpisodeViewHeader5\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n    var episode: EpisodeViewModel\\n\\n    @State private var state = DownloadManager.State.idle\\n\\n    private var downloadButton: some View { ... }\\n\\n    private var pendingView: some View { ... }\\n\\n    var body: some View {\\n        HStack {\\n            Text(episode.date)\\n            Spacer()\\n            createDownloadButton()\\n        }\\n    }\\n\\n    @ViewBuilder\\n    private func createDownloadButton() -> some View {\\n        switch state {\\n        case .idle:\\n        downloadButton\\n        case .pending:\\n            pendingView\\n        case .downloading(let progress):\\n            // highlight-start\\n            createProgressView(progress)\\n            // highlight-end\\n        case .done (let success):\\n            if success {\\n\\n            } else {\\n                downloadButton\\n            }\\n        }\\n    }\\n\\n    // highlight-start\\n    private func createProgressView(_ progress: CGFloat) -> some View {\\n\\n    }\\n    // highlight-end\\n\\n    private func downloadEpisode() {\\n\\n    }\\n\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"Next, we create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"progressView\"), \" returned in the progress case block passing in the progress value.\"), mdx(\"hr\", null), mdx(\"p\", null, \"We will create the actual progress view later. For now, let's just put this button as placeholder.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateEpisodeViewHeader6\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n    var episode: EpisodeViewModel\\n\\n    @State private var state = DownloadManager.State.idle\\n\\n    // highlight-start\\n    @EnvironmentObject private var downloadManager: DownloadManager\\n    // highlight-end\\n\\n    private var downloadButton: some View { ... }\\n\\n    private var pendingView: some View { ... }\\n\\n    var body: some View {\\n        HStack {\\n            Text(episode.date)\\n            Spacer()\\n            createDownloadButton()\\n        }\\n    }\\n\\n    @ViewBuilder\\n    private func createDownloadButton() -> some View { ... }\\n\\n    // highlight-start\\n    private func createProgressView(_ progress: CGFloat) -> some View {\\n        Button(action: abort, label: {\\n            Text(\\\"\\\\(progress)\\\")\\n        })\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    private func abort() {\\n        downloadManager.abort(name: episode.title)\\n    }\\n    // highlight-start\\n\\n    private func downloadEpisode() {\\n        // highlight-start\\n        downloadManager.download(episode)\\n        // highlight-start\\n    }\\n\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"We also create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"abort\"), \" method that we call in the progress button action, and replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"downloadEpisode\"), \" closure with that \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"downloadEpisode\"), \" function. You will need to remove the second parameter in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeView\"), \" .\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateEpisodeViewHeader7\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n    var episode: EpisodeViewModel\\n\\n    @State private var state = DownloadManager.State.idle\\n\\n    @EnvironmentObject private var downloadManager: DownloadManager\\n\\n    private var downloadButton: some View {\\n        Button(action: downloadEpisode, label: {\\n            Image(systemName: \\\"icloud.and.arrow.down.fill\\\")\\n                .resizable()\\n                .scaledToFit()\\n                .foregroundColor(.border)\\n        })\\n    }\\n\\n    private var pendingView: some View {\\n        Text(\\\"Pending...\\\")\\n            .font(.callout)\\n            .foregroundColor(.gray)\\n    }\\n\\n    // highlight-start\\n    private var savedImageView: some View {\\n        Image(systemName: \\\"icloud.fill\\\")\\n            .resizable()\\n            .scaledToFit()\\n            .frame(width: 20, height: 20)\\n            .foregroundColor(.accentColor)\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        HStack {\\n            Text(episode.date)\\n            Spacer()\\n            createDownloadButton()\\n        }\\n    }\\n\\n    @ViewBuilder\\n    private func createDownloadButton() -> some View {\\n        switch state {\\n        case .idle:\\n            downloadButton\\n        case .pending:\\n            pendingView\\n        case .downloading(let progress):\\n            createProgressView(progress)\\n        case .done (let success):\\n            if success {\\n                // highlight-start\\n                savedImageView\\n                // highlight-end\\n            } else {\\n                downloadButton\\n            }\\n        }\\n    }\\n\\n    private func createProgressView(_ progress: CGFloat) -> some View {\\n        Button(action: abort, label: {\\n            Text(\\\"\\\\(progress)\\\")\\n        })\\n    }\\n\\n    private func abort() {\\n        downloadManager.abort(name: episode.title)\\n    }\\n\\n    private func downloadEpisode() {\\n        downloadManager.download(episode)\\n    }\\n\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"We also create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"abort\"), \" method that we call in the progress button action, and replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"downloadEpisode\"), \" closure with that \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"downloadEpisode\"), \" function. You will need to remove the second parameter in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeView\"), \" .\"), mdx(\"h2\", null, \"Updating the DownloadManager\"), mdx(\"p\", null, \"Add the following function in the download manager class that returns a state for a given episode.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateDownloadManager1\\n// $$liquidcoderFilename-DownloadManager\\n\\nimport Foundation\\nimport SwiftUI\\n\\nclass DownloadManager: ObservableObject {\\n\\n    ...\\n\\n    // highlight-start\\n    func getState(for episode: EpisodeViewModel) -> State {\\n        if let state = downloadings[episode.title] {\\n            return state\\n        }\\n\\n        let episode = environment.coredata.fetchEpisode(episode)\\n        self.state = .done(success: episode != nil)\\n        return .done(success: episode != nil)\\n    }\\n    // highlight-end\\n\\n    ...\\n}\\n\")), mdx(\"p\", null, \"Here we first check if the current episode is being downloaded, and if so, we return the state, otherwise we check if it's already saved, and return a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".done\"), \" with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"true\"), \" or \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"false\"), \" depending on whether the episode is saved or not.\"), mdx(\"p\", null, \"We want to update the local state whenever this view appears, so we get its state from the download manager using its episode view model.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateEpisodeViewHeader8\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n    var episode: EpisodeViewModel\\n\\n    @State private var state = DownloadManager.State.idle\\n\\n    @EnvironmentObject private var downloadManager: DownloadManager\\n\\n    private var downloadButton: some View { ... }\\n\\n    private var pendingView: some View { ... }\\n\\n    private var savedImageView: some View { ... }\\n\\n    var body: some View {\\n        HStack {\\n            Text(episode.date)\\n            Spacer()\\n            createDownloadButton()\\n\\n        // highlight-start\\n        }.onAppear {\\n            state = downloadManager.getState(for: episode)\\n        }\\n        // highlight-end\\n    }\\n\\n    @ViewBuilder\\n    private func createDownloadButton() -> some View {\\n        switch state {\\n        case .idle:\\n            downloadButton\\n        case .pending:\\n            pendingView\\n        case .downloading(let progress):\\n            createProgressView(progress)\\n        case .done (let success):\\n            if success {\\n                savedImageView\\n            } else {\\n                downloadButton\\n            }\\n        }\\n    }\\n\\n    private func progressView(_ progress: CGFloat) -> some View {\\n        Button(action: abort, label: {\\n            Text(\\\"\\\\(progress)\\\")\\n        })\\n    }\\n\\n    private func abort() {\\n        downloadManager.abort(name: episode.title)\\n    }\\n\\n    private func downloadEpisode() {\\n        downloadManager.download(episode)\\n    }\\n\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"And finally, we want to listen to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"downloadings\"), \" changes, and update the state accordingly. \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateEpisodeViewHeader9\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n    var episode: EpisodeViewModel\\n\\n    @State private var state = DownloadManager.State.idle\\n\\n    @EnvironmentObject private var downloadManager: DownloadManager\\n\\n    private var downloadButton: some View {\\n        Button(action: downloadEpisode, label: {\\n            Image(systemName: \\\"icloud.and.arrow.down.fill\\\")\\n                .resizable()\\n                .scaledToFit()\\n                .foregroundColor(.border)\\n        })\\n    }\\n\\n    private var pendingView: some View {\\n        Text(\\\"Pending...\\\")\\n            .font(.callout)\\n            .foregroundColor(.gray)\\n    }\\n\\n    private var savedImageView: some View {\\n        Image(systemName: \\\"icloud.fill\\\")\\n            .resizable()\\n            .scaledToFit()\\n            .frame(width: 20, height: 20)\\n            .foregroundColor(.accentColor)\\n    }\\n\\n    var body: some View {\\n        HStack {\\n            Text(episode.date)\\n            Spacer()\\n            createDownloadButton()\\n\\n        }.onAppear {\\n            state = downloadManager.getState(for: episode)\\n            // highlight-start\\n        }.onChange(of: downloadManager.downloadings, perform: { downloadings in\\n            if let state = downloadings[episode.title] {\\n                self.state = state\\n            } else {\\n                self.state =  downloadManager.getState(for: episode)\\n            }\\n        })\\n        // highlight-end\\n    }\\n\\n    @ViewBuilder\\n    private func createDownloadButton() -> some View {\\n        switch state {\\n        case .idle:\\n            downloadButton\\n        case .pending:\\n            pendingView\\n        case .downloading(let progress):\\n            progressView(progress)\\n        case .done (let success):\\n            if success {\\n                savedImageView\\n            } else {\\n                downloadButton\\n            }\\n        }\\n    }\\n\\n    private func progressView(_ progress: CGFloat) -> some View {\\n        Button(action: abort, label: {\\n            Text(\\\"\\\\(progress)\\\")\\n        })\\n    }\\n\\n    private func abort() {\\n        downloadManager.abort(name: episode.title)\\n    }\\n\\n    private func downloadEpisode() {\\n        downloadManager.download(episode)\\n    }\\n\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n            // highlight-start\\n        }.environmentObject(DownloadManager())\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"So, we need to check if the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"downloadings\"), \" dictionary contains a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \"  for the current episode, then update the local state, otherwise we need to check if the episode is already save or not.\"), mdx(\"h2\", null, \"Updating the StreamCastApp\"), mdx(\"p\", null, \"In order to navigate to the episodes screen, you will need to add the following to the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"StreamCastApp.swift\"), \" file.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateStreamCastApp1\\n// $$liquidcoderFilename-StreamCastApp\\n\\nimport SwiftUI\\n\\n@main\\nstruct StreamCastApp: App {\\n    let persistenceController = PersistenceController.shared\\n    @ObservedObject private var store = Store()\\n    // highlight-start\\n    @ObservedObject private var downloadManager = DownloadManager()\\n    // highlight-end\\n\\n    var body: some Scene {\\n        WindowGroup {\\n            ContentView()\\n                .environmentObject(store)\\n                // highlight-start\\n                .environmentObject(downloadManager)\\n                // highlight-end\\n                .environment(\\\\.managedObjectContext, persistenceController.container.viewContext)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"And this bring the lesson #20 to close guys. See you in #21 where we will finish creating the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeViewHeader\"), \" and update \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailScreen\"), \". Happy coding!!!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#updating-the-coredata-service-protocol","title":"Updating the coredata service protocol"},{"url":"#updating-the-preview-coredata-service","title":"Updating the preview coredata service"},{"url":"#updating-the-coredata-service","title":"Updating the coredata service"},{"url":"#update-the-episodeviewheader","title":"Update the EpisodeViewHeader"},{"url":"#updating-the-downloadmanager","title":"Updating the DownloadManager"},{"url":"#updating-the-streamcastapp","title":"Updating the StreamCastApp"}]}}}},{"createdDate":null,"createdAt":"2021-03-17T09:12:26.549Z","isNewLayout":true,"previews":{"previews":"{\n\t\"Lesson21CircularProgressView1\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\t\"Lesson21CircularProgressView2\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\t\"Lesson21CircularProgressView3\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1615623870/StreamCast/Lesson%2021/morzesshypjg9rhqvg7q.png\"],\n\t\"Lesson21CircularProgressView4\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1615623870/StreamCast/Lesson%2021/morzesshypjg9rhqvg7q.png\"],\n\t\"Lesson21CircularProgressView6\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1615623870/StreamCast/Lesson%2021/osflcl63ulpyqkfguyfx.png\"],\n\t\"Lesson21UpdateEpisodeViewHeader2\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1615634374/StreamCast/Lesson%2021/doqhd9mjtmxnekuq5ttv.gif\"]\n}"},"slug":"lesson-21-download-feature-finale","title":"Lesson #21: Download Feature Finale","updatedDate":"2021-03-17T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZV4PBXZ0AOK9vR2d6R7AWw2J07UKhug0UPk","type":"zip","id":"233a6558-4ec6-5977-8bd4-c69e312f03f4","name":"Source code"}],"allChapters":null,"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21CircularProgressView1\\n// $$liquidcoderFilename-CircularProgressView\\n\\nimport SwiftUI\\n\\nstruct CircularProgressView: View {\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct CircularProgressView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        CircularProgressView()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21CircularProgressView2\\n// $$liquidcoderFilename-CircularProgressView\\n\\nimport SwiftUI\\n\\nstruct CircularProgressView: View {\\n\\n    // highlight-start\\n    var progress: CGFloat\\n    var total: CGFloat = 100\\n    var width: CGFloat = 10\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct CircularProgressView_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        // highlight-start\\n        Group {\\n            CircularProgressView(progress: 40)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            CircularProgressView(progress: 40)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21CircularProgressView3\\n// $$liquidcoderFilename-CircularProgressView\\n\\nimport SwiftUI\\n\\nstruct CircularProgressView: View {\\n\\n    var progress: CGFloat\\n    var total: CGFloat = 100\\n    var width: CGFloat = 10\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        ProgressView(value: progress, total: total)\\n        // highlight-end\\n\\n    }\\n}\\n\\nstruct CircularProgressView_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        Group {\\n            CircularProgressView(progress: 40)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            CircularProgressView(progress: 40)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21CircularProgressView4\\n// $$liquidcoderFilename-CircularProgressView\\n\\nimport SwiftUI\\n\\nstruct CircularProgressView: View {\\n\\n    var progress: CGFloat\\n    var total: CGFloat = 100\\n    var width: CGFloat = 10\\n\\n    var body: some View {\\n\\n        ProgressView(value: progress, total: total)\\n            // highlight-start\\n            .progressViewStyle(CircularProgressViewStyle(width: width))\\n        // highlight-end\\n\\n    }\\n}\\n\\nstruct CircularProgressView_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        Group {\\n            CircularProgressView(progress: 40)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            CircularProgressView(progress: 40)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21CircularProgressView5\\n// $$liquidcoderFilename-CircularProgressView\\n\\nimport Foundation\\n// highlight-start\\nimport SwiftUI\\n// highlight-end\\n\\n// highlight-start\\npublic struct CircularProgressViewStyle : ProgressViewStyle {\\n\\n    public func makeBody(configuration: LinearProgressViewStyle.Configuration) -> some View {\\n\\n    }\\n}\\n// highlight-end\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21CircularProgressView6\\n// $$liquidcoderFilename-CircularProgressView\\n\\nimport Foundation\\nimport SwiftUI\\n\\npublic struct CircularProgressViewStyle : ProgressViewStyle {\\n\\n    // highlight-start\\n    var width: CGFloat\\n    // highlight-end\\n\\n    public func makeBody(configuration: LinearProgressViewStyle.Configuration) -> some View {\\n\\n        // highlight-start\\n        VStack(spacing: 10) {\\n            configuration.label\\n                .foregroundColor(Color.secondary)\\n            ZStack {\\n                Circle()\\n                    .stroke(lineWidth: width)\\n                    .opacity(0.3)\\n                    .foregroundColor(Color.accentColor.opacity(0.5))\\n\\n                Circle()\\n                    .trim(from: 0.0, to: CGFloat(configuration.fractionCompleted ?? 0))\\n                    .stroke(style: StrokeStyle(lineWidth: width, lineCap: .round, lineJoin: .round))\\n                    .foregroundColor(Color.accentColor)\\n                    .rotationEffect(Angle(degrees: -90) )\\n            }\\n        }\\n        // highlight-end\\n\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21UpdateEpisodeViewHeader1\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n        ...\\n\\n    private func progressView(_ progress: CGFloat) -> some View {\\n        Button(action: abort, label: {\\n            // highlight-start\\n            CircularProgressView(progress: progress, total: 1, width: 3)\\n                .frame(width: 20, height: 20)\\n            // highlight-end\\n        })\\n    }\\n\\n    private func abort() {\\n        downloadManager.abort(name: episode.title)\\n    }\\n\\n    private func downloadEpisode() {\\n        downloadManager.download(episode)\\n    }\\n\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(DownloadManager())\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21UpdateEpisodeViewHeader2\\n// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n        ...\\n\\n    private func progressView(_ progress: CGFloat) -> some View {\\n        Button(action: abort, label: {\\n            ZStack {\\n                CircularProgressView(progress: progress, total: 1, width: 3)\\n                    .frame(width: 20, height: 20)\\n\\n                // highlight-start\\n                Image(systemName: \\\"stop.fill\\\")\\n                    .resizable()\\n                    .scaledToFit()\\n                    .frame(width: 8, height: 8)\\n                    .foregroundColor(.accentColor)\\n                // highlight-end\\n            }\\n        })\\n    }\\n\\n    private func abort() {\\n        downloadManager.abort(name: episode.title)\\n    }\\n\\n    private func downloadEpisode() {\\n        downloadManager.download(episode)\\n    }\\n\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(DownloadManager())\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21UpdateAction1\\n// $$liquidcoderFilename-Action\\n\\nimport Foundation\\n\\nenum Action {\\n    enum Api {\\n        case fetchPodcasts(_ term: String, limit: Int = 10)\\n        case fetchGenres(_ filename: String)\\n        case updatePodcast(_ podcast: PodcastViewModel)\\n    }\\n\\n    enum Coredata {\\n        case fetchGenres\\n        case saveGenres(_ genres: [GenreViewModel])\\n        // highlight-start\\n        case fetchPodcast(_ podcast: PodcastViewModel)\\n        // highlight-end\\n    }\\n\\n    case api(_ action: Api)\\n    case coredata(_ action: Coredata)\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21UpdateAppState1\\n// $$liquidcoderFilename-AppState\\n\\nimport Foundation\\nimport SwiftUI\\n\\nenum AppState {\\n\\n        ...\\n\\n    struct  Api {\\n        var preferredPodcasts: [String: Result<[PodcastViewModel]>] = [:]\\n        var allGenres: Result<[GenreViewModel]> = .loading\\n        var podcast: Result<PodcastViewModel> = .loading\\n    }\\n\\n    struct Coredata {\\n        var savedGenres: [GenreViewModel] = []\\n        // highlight-start\\n        var savedPodcast: Result<PodcastViewModel> = .loading\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21UpdatePodcastManagedObject1\\n// $$liquidcoderFilename-PodcastManagedObject\\n\\nimport Foundation\\nimport CoreData\\n\\nextension PodcastManagedObject {\\n\\n    @nonobjc public class func fetchRequest() -> NSFetchRequest<PodcastManagedObject> {\\n        return NSFetchRequest<PodcastManagedObject>(entityName: \\\"PodcastManagedObject\\\")\\n    }\\n\\n    @NSManaged public var author: String?\\n    @NSManaged public var country: String?\\n    @NSManaged public var createdAt: Date?\\n    @NSManaged public var descr: String?\\n    @NSManaged public var explicitContent: String?\\n    @NSManaged public var feedUrl: String?\\n    @NSManaged public var genres: String?\\n    @NSManaged public var id: Int64\\n    @NSManaged public var image: Data?\\n    @NSManaged public var imageUrl: String?\\n    @NSManaged public var isBookmarked: Bool\\n    @NSManaged public var language: String?\\n    @NSManaged public var releaseDate: Date?\\n    @NSManaged public var thumbnail: Data?\\n    @NSManaged public var thumbnailUrl: String?\\n    @NSManaged public var title: String?\\n    @NSManaged public var totalEpisodes: Int64\\n    @NSManaged public var episodes: NSSet?\\n\\n    // highlight-start\\n    public var episodesArray: [EpisodeManagedObject] {\\n        guard let episodeSet = episodes as?  Set<EpisodeManagedObject> else {\\n            return []\\n        }\\n\\n        return episodeSet.sorted {\\n            $0.createdAt! > $1.createdAt!\\n        }\\n    }\\n    // highlight-end\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21UpdatePodcastViewModel1\\n// $$liquidcoderFilename-PodcastViewModel\\n\\nimport Foundation\\nimport FeedKit\\n\\nstruct PodcastViewModel: Identifiable, Equatable {\\n\\n      ...\\n\\n    // highlight-start\\n    init(_ managedPodcast: PodcastManagedObject, existingPodcast: PodcastViewModel? = nil) {\\n        self.id =  Int(managedPodcast.id)\\n        self.title = managedPodcast.title ?? \\\"\\\"\\n        self.imageUrl = managedPodcast.imageUrl ?? \\\"\\\"\\n        self.author = managedPodcast.author ?? \\\"\\\"\\n        self.thumbnailUrl = managedPodcast.thumbnailUrl ?? \\\"\\\"\\n        self.image = managedPodcast.image ?? Data()\\n        self.thumbnail =  managedPodcast.thumbnail ?? Data()\\n        self.totalEpisodes =  managedPodcast.episodes?.count ?? 0\\n        self.explicitContent = managedPodcast.explicitContent ?? \\\"\\\"\\n        self.description = managedPodcast.descr ?? \\\"\\\"\\n        self.language = managedPodcast.language ?? \\\"\\\"\\n        self.country = managedPodcast.country ?? \\\"\\\"\\n                self.publisher = managedPodcast.publisher ?? \\\"\\\"\\n        self.genres = managedPodcast.genres?.components(separatedBy: \\\", \\\") ?? []\\n        self.nextEpisodePubDate = Date()\\n        self.feedUrl = managedPodcast.feedUrl ?? \\\"\\\"\\n        self.episodes = existingPodcast?.episodes ?? []\\n        self.isBookmarked = managedPodcast.isBookmarked\\n    }\\n    // highlight-end\\n\\n        ...\\n\\n    static func == (lhs: PodcastViewModel, rhs: PodcastViewModel) -> Bool {\\n        return lhs.id == rhs.id\\n            && lhs.language == rhs.language\\n            && lhs.episodes.count == rhs.episodes.count\\n            && lhs.image.count == rhs.image.count\\n            && lhs.thumbnail.count == rhs.thumbnail.count\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21UpdateEpisodeViewModel1\\n// $$liquidcoderFilename-EpisodeViewModel\\n\\nimport Foundation\\nimport FeedKit\\n\\nstruct EpisodeViewModel: Identifiable {\\n\\n    // highlight-start\\n    var id = UUID()\\n    // highlight-end\\n    var title, description: String\\n    var date: String\\n    var audioUrl: String\\n    var audio: Data?\\n    var audioLength: Double\\n    var audioSize: String\\n    var podcast: PodcastViewModel = .placeholder\\n\\n        ...\\n\\n    // highlight-start\\n    init(downloaded: EpisodeManagedObject, podcast: PodcastViewModel) {\\n        self.id = downloaded.id ?? UUID()\\n        self.title = downloaded.title ?? \\\"\\\"\\n        self.description = downloaded.descr ?? \\\"\\\"\\n        self.date = downloaded.releaseDate?.format() ?? \\\"\\\"\\n        self.audioUrl = downloaded.sourceAudioUrl ?? \\\"\\\"\\n        self.audioLength = downloaded.duration\\n        self.audioSize = downloaded.size ?? \\\"\\\"\\n        self.audio = downloaded.audio\\n        self.podcast = podcast\\n    }\\n    // highlight-end\\n\\n    init(feedItem: RSSFeedItem, podcast: PodcastViewModel) {\\n        self.title = feedItem.title ?? \\\"\\\"\\n        self.description = feedItem.description ?? \\\"\\\"\\n        self.date = feedItem.pubDate.map { $0.format() } ?? \\\"\\\"\\n        self.audioUrl = feedItem.enclosure?.attributes?.url ?? \\\"\\\"\\n        self.audioLength = feedItem.iTunes?.iTunesDuration ?? 0\\n\\n        self.audioSize = feedItem.enclosure?.attributes?.length.map {\\n            \\\"\\\\($0 / 1000000) mb\\\"\\n        } ?? \\\"0 mb\\\"\\n\\n        self.podcast = podcast\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21UpdatePodcastViewModel3\\n// $$liquidcoderFilename-PodcastViewModel\\n\\nimport Foundation\\nimport FeedKit\\n\\nstruct PodcastViewModel: Identifiable, Equatable {\\n\\n        ...\\n\\n    init(_ managedPodcast: PodcastManagedObject, existingPodcast: PodcastViewModel? = nil) {\\n        self.id =  Int(managedPodcast.id)\\n        self.title = managedPodcast.title ?? \\\"\\\"\\n        self.imageUrl = managedPodcast.imageUrl ?? \\\"\\\"\\n        self.author = managedPodcast.author ?? \\\"\\\"\\n        self.thumbnailUrl = managedPodcast.thumbnailUrl ?? \\\"\\\"\\n        self.image = managedPodcast.image ?? Data()\\n        self.thumbnail =  managedPodcast.thumbnail ?? Data()\\n        self.totalEpisodes =  managedPodcast.episodes?.count ?? 0\\n        self.explicitContent = managedPodcast.explicitContent ?? \\\"\\\"\\n        self.description = managedPodcast.descr ?? \\\"\\\"\\n        self.language = managedPodcast.language ?? \\\"\\\"\\n        self.country = managedPodcast.country ?? \\\"\\\"\\n        self.genres = managedPodcast.genres?.components(separatedBy: \\\", \\\") ?? []\\n        self.nextEpisodePubDate = Date()\\n        self.feedUrl = managedPodcast.feedUrl ?? \\\"\\\"\\n        // highlight-start\\n        self.episodes = existingPodcast?.episodes.map({ episode  in\\n            if let managedEpisode = managedPodcast.episodesArray.first(where: { $0.title == episode.title }){\\n                return EpisodeViewModel(downloaded: managedEpisode, podcast: self)\\n            } else {\\n                return episode\\n            }\\n        }) ?? []\\n        // highlight-end\\n        self.isBookmarked = managedPodcast.isBookmarked\\n    }\\n\\n    ...\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21UpdateCoredataReducer1\\n// $$liquidcoderFilename-CoredataReducer\\n\\nimport Foundation\\n\\nstruct CoredataReducer {\\n\\n    static func execute(action: Action.Coredata, state: inout AppState.Coredata, environment: AppEnvironment) {\\n\\n        switch action {\\n\\n        case .fetchGenres:\\n            state.savedGenres = environment.coredata.fetchGenres()\\n        case .saveGenres(let genres):\\n            _ = environment.coredata.saveGenres(genres)\\n        case .fetchPodcast(let podcast):\\n        // highlight-start\\n            if let managedPodcast = environment.coredata.fetchPodcast(podcast.id) {\\n                let podcast = PodcastViewModel(managedPodcast, existingPodcast: podcast)\\n                state.savedPodcast = .success(podcast)\\n            } else {\\n                state.savedPodcast = .failure\\n            }            \\n        // highlight-end\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21UpdatePodcastDetailScreen1\\n// $$liquidcoderFilename-PodcastDetailScreen\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailScreen: View {\\n\\n        ...\\n\\n    var body: some View {\\n\\n        ZStack {\\n                    ...\\n        }.onAppear { \\n            checkState()\\n        }.onChange(of: store.apiState.podcast) { _ in\\n            checkState()\\n                // highlight-start\\n        }.onChange(of: store.coredataState.savedPodcast) { _ in\\n            checkState()\\n        }\\n                // highlight-end\\n    }\\n\\n    private func checkState() {\\n        if case .success(let podcast) = store.apiState.podcast,\\n           podcast.title == self.podcast.title {\\n            // highlight-start\\n            checkCoredataState(for: podcast)\\n            // highlight-end\\n        } else {\\n            store.dispatch(.api(.updatePodcast(podcast)))\\n        }\\n    }\\n\\n    // highlight-start\\n    private func checkCoredataState(for podcast: PodcastViewModel) {\\n        switch store.coredataState.savedPodcast {\\n        case .loading:\\n            break\\n        case .success(_):\\n            break\\n        case .failure:\\n            break\\n        }\\n    }\\n    // highlight-end\\n}\\n\\n        ...\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21UpdatePodcastDetailScreen2\\n// $$liquidcoderFilename-PodcastDetailScreen\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailScreen: View {\\n\\n        ...\\n\\n    var body: some View {\\n\\n        ZStack {\\n           ...\\n        }.onAppear { \\n            checkState()\\n        }.onChange(of: store.apiState.podcast) { _ in\\n            checkState()\\n        }.onChange(of: store.coredataState.savedPodcast) { _ in\\n            checkState()\\n        }\\n    }\\n\\n    private func checkState() {\\n        if case .success(let podcast) = store.apiState.podcast,\\n           podcast.title == self.podcast.title {\\n            checkCoredataState(for: podcast)\\n        } else {\\n            store.dispatch(.api(.updatePodcast(podcast)))\\n            Log.info(\\\"Dispatching update podcast\\\")\\n        }\\n    }\\n\\n    private func checkCoredataState(for podcast: PodcastViewModel) {\\n        switch store.coredataState.savedPodcast {\\n        case .loading:\\n            // highlight-start\\n            store.dispatch(.coredata(.fetchPodcast(podcast)))\\n            // highlight-end\\n        case .success(_):\\n            break\\n        case .failure:\\n            // highlight-start\\n            state = store.apiState.podcast\\n        // highlight-end\\n        }\\n    }\\n}\\n\\n    ...\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21UpdatePodcastDetailScreen1\\n// $$liquidcoderFilename-PodcastDetailScreen\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailScreen: View {\\n\\n        ...\\n\\n    var body: some View {\\n\\n        ZStack {\\n            ...\\n        }.onAppear { \\n            checkState()\\n        }.onChange(of: store.apiState.podcast) { _ in\\n            checkState()\\n        }.onChange(of: store.coredataState.savedPodcast) { _ in\\n            checkState()\\n        }\\n    }\\n\\n    private func checkState() {\\n        if case .success(let podcast) = store.apiState.podcast,\\n           podcast.title == self.podcast.title {\\n            checkCoredataState(for: podcast)\\n        } else {\\n            store.dispatch(.api(.updatePodcast(podcast)))\\n        }\\n    }\\n\\n    private func checkCoredataState(for podcast: PodcastViewModel) {\\n        switch store.coredataState.savedPodcast {\\n        case .loading:\\n            store.dispatch(.coredata(.fetchPodcast(podcast)))\\n        case .success(let savedPodcast):\\n            // highlight-start\\n            if savedPodcast.title == self.podcast.title {\\n                state = store.coredataState.savedPodcast\\n            } else {\\n                store.dispatch(.coredata(.fetchPodcast(podcast)))\\n            }\\n            // highlight-end\\n        case .failure:\\n            state = store.apiState.podcast\\n        }\\n    }\\n}\\n\\n    ...\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson21Intro-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey, welcome to lesson #21. In the previous one, we finished implementing the download feature. In this one, let's create the missing download progress view and make few updates to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailScreen\"), \".\")), mdx(\"div\", {\n    id: \"Lesson21CircularProgressView1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"CircularProgressView\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI\\u2192Elements\")), \" folder, add a new swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"CircularProgressView.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS, macOS and watchOS-Extension targets\"))), mdx(\"div\", {\n    id: \"Lesson21CircularProgressView2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We first declare properties that we will utilise with the progress view.\"), mdx(\"hr\", null), mdx(\"p\", null, \"In the preview, we set \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"progress\"), \" to be 40 and the total is 100 which makes the progress 40% done.\")), mdx(\"div\", {\n    id: \"Lesson21CircularProgressView3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next, we replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" with an actual \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ProgressView\"), \". \")), mdx(\"div\", {\n    id: \"Lesson21CircularProgressView4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And right now, we are just displaying an horizontal progress view. To make it circular, we will need to create that missing custom style applied to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ProgressView\"), \".\"), mdx(\"hr\", null), mdx(\"p\", null, \"Let's create that custom style now.\")), mdx(\"div\", {\n    id: \"Lesson21CircularProgressViewStyle1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"CircularProgressViewStyle\"), mdx(\"p\", null, \"Create a simple swift file inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ShareUI \\u2192 Styles\")), \" folder named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"CircularProgressViewStyle\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS, macOS and watchOS-Extension targets\"))), mdx(\"div\", {\n    id: \"Lesson21CircularProgressView5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We first import \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SwiftUI\"), \".\"), mdx(\"p\", null, \"Then create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CircularProgressViewStyle\"), \" struct conforming it to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ProgressViewStyle\"), \" for it to be used as a style.\"), mdx(\"p\", null, \"That protocol requires us to implement the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"makeBody\"), \" method that gives us a configuration like the other style protocols, and returns a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"View\"))), mdx(\"div\", {\n    id: \"Lesson21CircularProgressView6-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We expose the width to the calling site, then inside \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"makeBody\"), \", we create a circular view that just puts 2 stroked circles on top of each other where the top one displays the progress and the bottom one just creates a track for it.\")), mdx(\"div\", {\n    id: \"Lesson21UpdateEpisodeViewHeader1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the EpisodeViewHeader\"), mdx(\"p\", null, \"Having the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ProgressView\"), \", we are now able to finish the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeViewHeader\"), \", so let's go back inside it, and replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" with the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CircularProgressView\"))), mdx(\"div\", {\n    id: \"Lesson21UpdateEpisodeViewHeader2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"When downloading a particular episode, we want to be able to stop the process when the user taps on the progress view, so we add a small stop image indicating that the user can actually abort the download process when clicked.\")), mdx(\"div\", {\n    id: \"Lesson21UpdateAction1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the Action\"), mdx(\"p\", null, \"We want to be able to dispatch a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"coredata\"), \" \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"action\"), \" to fetch a given podcast.\")), mdx(\"div\", {\n    id: \"Lesson21UpdateAppState1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the AppState\"), mdx(\"p\", null, \"Add the highlighted line in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppState\"), \"'s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Coredata\"), \" struct\")), mdx(\"div\", {\n    id: \"Lesson21UpdatePodcastManagedObject1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the PodcastManagedObject\"), mdx(\"p\", null, \"We need to convert the episodes set to a swift array to be used comfortably. \")), mdx(\"div\", {\n    id: \"Lesson21UpdatePodcastViewModel1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the PodcastViewModel\"), mdx(\"p\", null, \"Create another overload of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"init\"), \" method.\")), mdx(\"div\", {\n    id: \"Lesson21UpdateEpisodeViewModel1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the EpisodeViewModel\"), mdx(\"p\", null, \"First replace the existing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"id\"), \" declaration, and create another overload of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"init\"), \" method \")), mdx(\"div\", {\n    id: \"Lesson21UpdatePodcastViewModel2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now that we have created that overload initialiser in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeViewModel\"), \", let's now update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastViewModel\"), \".\")), mdx(\"div\", {\n    id: \"Lesson21UpdatePodcastViewModel3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We are looping over the existing podcast's episodes array, and checking whether each of them exist in the managed podcast's episodes array. If they do, we create and return a new updated episode, otherwise we return the existing episode\")), mdx(\"div\", {\n    id: \"Lesson21UpdateCoredataReducer1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the Coredata reducer\"), mdx(\"p\", null, \"As always let's make some change in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CoredataReducer\"), \" struct. \"), mdx(\"hr\", null), mdx(\"p\", null, \"So here, we are fetching a podcast given its id, then update the state appropriately.\")), mdx(\"div\", {\n    id: \"Lesson21UpdatePodcastDetailScreen1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the PodcastDetailScreen\"), mdx(\"p\", null, \"Beside observing changes for the api's podcast state, we also want to watch for the changes to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"coredata\"), \" \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"savedPodcast\"), \".\"), mdx(\"p\", null, \"We replace the state update line with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"checkCoredataState\"), \" method which will eventually update the state.\")), mdx(\"div\", {\n    id: \"Lesson21UpdatePodcastDetailScreen2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"So, the deal here is that we fetch the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"coredata\"), \" \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" for a particular podcast if the state is loading which basically means it's unknown.\"), mdx(\"p\", null, \"If the state is a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"failure\"), \", it means there's no saved version for this podcast, so we just set it to the existing api state.\")), mdx(\"div\", {\n    id: \"Lesson21UpdatePodcastDetailScreen3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"If we have a valid podcast in the global coredata state which is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"savedPodcast\"), \", we check whether it's the same as what has been clicked by the user, then set it to the local state, otherwise we dispatch another action to update to the current podcast.\")), mdx(\"div\", {\n    id: \"Lesson21Outro-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And this is the end of lesson #21, see you in #22 where we will start working on the bookmarking feature. Happy coding guys!\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#circularprogressview","title":"CircularProgressView"},{"url":"#circularprogressviewstyle","title":"CircularProgressViewStyle"},{"url":"#updating-the-episodeviewheader","title":"Updating the EpisodeViewHeader"},{"url":"#updating-the-action","title":"Updating the Action"},{"url":"#updating-the-appstate","title":"Updating the AppState"},{"url":"#updating-the-podcastmanagedobject","title":"Updating the PodcastManagedObject"},{"url":"#updating-the-podcastviewmodel","title":"Updating the PodcastViewModel"},{"url":"#updating-the-episodeviewmodel","title":"Updating the EpisodeViewModel"},{"url":"#updating-the-coredata-reducer","title":"Updating the Coredata reducer"},{"url":"#updating-the-podcastdetailscreen","title":"Updating the PodcastDetailScreen"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey, welcome to lesson #21. In the previous one, we finished implementing the download feature. In this one, let's create the missing download progress view and make few updates to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailScreen\"), \".\"), mdx(\"h2\", null, \"CircularProgressView\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI\\u2192Elements\")), \" folder, add a new swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"CircularProgressView.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS, macOS and watchOS-Extension targets\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-CircularProgressView\\n\\nimport SwiftUI\\n\\nstruct CircularProgressView: View {\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct CircularProgressView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        CircularProgressView()\\n    }\\n}\\n\")), mdx(\"p\", null, \"We first declare properties that we will utilise with the progress view.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-CircularProgressView\\n\\nimport SwiftUI\\n\\nstruct CircularProgressView: View {\\n\\n    // highlight-start\\n    var progress: CGFloat\\n    var total: CGFloat = 100\\n    var width: CGFloat = 10\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct CircularProgressView_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        // highlight-start\\n        Group {\\n            CircularProgressView(progress: 40)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            CircularProgressView(progress: 40)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"In the preview, we set \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"progress\"), \" to be 40 and the total is 100 which makes the progress 40% done.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-CircularProgressView\\n\\nimport SwiftUI\\n\\nstruct CircularProgressView: View {\\n\\n    var progress: CGFloat\\n    var total: CGFloat = 100\\n    var width: CGFloat = 10\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        ProgressView(value: progress, total: total)\\n        // highlight-end\\n\\n    }\\n}\\n\\nstruct CircularProgressView_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        Group {\\n            CircularProgressView(progress: 40)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            CircularProgressView(progress: 40)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"Next, we replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" with an actual \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ProgressView\"), \". \"), mdx(\"p\", null, \"And right now, we are just displaying an horizontal progress view. To make it circular, we will need to create that missing custom style applied to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ProgressView\"), \".\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-CircularProgressView\\n\\nimport SwiftUI\\n\\nstruct CircularProgressView: View {\\n\\n    var progress: CGFloat\\n    var total: CGFloat = 100\\n    var width: CGFloat = 10\\n\\n    var body: some View {\\n\\n        ProgressView(value: progress, total: total)\\n            // highlight-start\\n            .progressViewStyle(CircularProgressViewStyle(width: width))\\n        // highlight-end\\n\\n    }\\n}\\n\\nstruct CircularProgressView_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        Group {\\n            CircularProgressView(progress: 40)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            CircularProgressView(progress: 40)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \" Let's create that custom style now.\"), mdx(\"h2\", null, \"CircularProgressViewStyle\"), mdx(\"p\", null, \"Create a simple swift file inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ShareUI \\u2192 Styles\")), \" folder named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"CircularProgressViewStyle\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS, macOS and watchOS-Extension targets\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-CircularProgressView\\n\\nimport Foundation\\n// highlight-start\\nimport SwiftUI\\n// highlight-end\\n\\n// highlight-start\\npublic struct CircularProgressViewStyle : ProgressViewStyle {\\n\\n    public func makeBody(configuration: LinearProgressViewStyle.Configuration) -> some View {\\n\\n    }\\n}\\n// highlight-end\\n\")), mdx(\"p\", null, \"We first import \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SwiftUI\"), \".\"), mdx(\"p\", null, \"Then create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CircularProgressViewStyle\"), \" struct conforming it to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ProgressViewStyle\"), \" for it to be used as a style.\"), mdx(\"p\", null, \"That protocol requires us to implement the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"makeBody\"), \" method that gives us a configuration like the other style protocols, and returns a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"View\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-CircularProgressView\\n\\nimport Foundation\\nimport SwiftUI\\n\\npublic struct CircularProgressViewStyle : ProgressViewStyle {\\n\\n    // highlight-start\\n    var width: CGFloat\\n    // highlight-end\\n\\n    public func makeBody(configuration: LinearProgressViewStyle.Configuration) -> some View {\\n\\n        // highlight-start\\n        VStack(spacing: 10) {\\n            configuration.label\\n                .foregroundColor(Color.secondary)\\n            ZStack {\\n                Circle()\\n                    .stroke(lineWidth: width)\\n                    .opacity(0.3)\\n                    .foregroundColor(Color.accentColor.opacity(0.5))\\n\\n                Circle()\\n                    .trim(from: 0.0, to: CGFloat(configuration.fractionCompleted ?? 0))\\n                    .stroke(style: StrokeStyle(lineWidth: width, lineCap: .round, lineJoin: .round))\\n                    .foregroundColor(Color.accentColor)\\n                    .rotationEffect(Angle(degrees: -90) )\\n            }\\n        }\\n        // highlight-end\\n\\n    }\\n}\\n\")), mdx(\"p\", null, \"We expose the width to the calling site, then inside \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"makeBody\"), \", we create a circular view that just puts 2 stroked circles on top of each other where the top one displays the progress and the bottom one just creates a track for it.\"), mdx(\"h2\", null, \"Updating the EpisodeViewHeader\"), mdx(\"p\", null, \"Having the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ProgressView\"), \", we are now able to finish the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeViewHeader\"), \", so let's go back inside it, and replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" with the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CircularProgressView\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n        ...\\n\\n    private func createProgressView(_ progress: CGFloat) -> some View {\\n        Button(action: abort, label: {\\n            // highlight-start\\n            CircularProgressView(progress: progress, total: 1, width: 3)\\n                .frame(width: 20, height: 20)\\n            // highlight-end\\n        })\\n    }\\n\\n    private func abort() {\\n        downloadManager.abort(name: episode.title)\\n    }\\n\\n    private func downloadEpisode() {\\n        downloadManager.download(episode)\\n    }\\n\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(DownloadManager())\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-EpisodeViewHeader\\n\\nimport SwiftUI\\n\\nstruct EpisodeViewHeader: View {\\n\\n        ...\\n\\n    private func createProgressView(_ progress: CGFloat) -> some View {\\n        Button(action: abort, label: {\\n            ZStack {\\n                CircularProgressView(progress: progress, total: 1, width: 3)\\n                    .frame(width: 20, height: 20)\\n\\n                // highlight-start\\n                Image(systemName: \\\"stop.fill\\\")\\n                    .resizable()\\n                    .scaledToFit()\\n                    .frame(width: 8, height: 8)\\n                    .foregroundColor(.accentColor)\\n                // highlight-end\\n            }\\n        })\\n    }\\n\\n    private func abort() {\\n        downloadManager.abort(name: episode.title)\\n    }\\n\\n    private func downloadEpisode() {\\n        downloadManager.download(episode)\\n    }\\n\\n}\\n\\nstruct EpisodeViewHeader_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            EpisodeViewHeader(episode: EpisodeViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(DownloadManager())\\n    }\\n}\\n\")), mdx(\"p\", null, \"When downloading a particular episode, we want to be able to stop the process when the user taps on the progress view, so we add a small stop image indicating that the user can actually abort the download process when clicked.\"), mdx(\"h2\", null, \"Updating the Action\"), mdx(\"p\", null, \"We want to be able to dispatch a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"coredata\"), \" \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"action\"), \" to fetch a given podcast.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-Action\\n\\nimport Foundation\\n\\nenum Action {\\n    enum Api {\\n        case fetchPodcasts(_ term: String, limit: Int = 10)\\n        case fetchGenres(_ filename: String)\\n        case updatePodcast(_ podcast: PodcastViewModel)\\n    }\\n\\n    enum Coredata {\\n        case fetchGenres\\n        case saveGenres(_ genres: [GenreViewModel])\\n        // highlight-start\\n        case fetchPodcast(_ podcast: PodcastViewModel)\\n        // highlight-end\\n    }\\n\\n    case api(_ action: Api)\\n    case coredata(_ action: Coredata)\\n\\n}\\n\")), mdx(\"h2\", null, \"Updating the AppState\"), mdx(\"p\", null, \"Add the highlighted line in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AppState\"), \"'s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Coredata\"), \" struct\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-AppState\\n\\nimport Foundation\\nimport SwiftUI\\n\\nenum AppState {\\n\\n        ...\\n\\n    struct  Api {\\n        var preferredPodcasts: [String: Result<[PodcastViewModel]>] = [:]\\n        var allGenres: Result<[GenreViewModel]> = .loading\\n        var podcast: Result<PodcastViewModel> = .loading\\n    }\\n\\n    struct Coredata {\\n        var savedGenres: [GenreViewModel] = []\\n        // highlight-start\\n        var savedPodcast: Result<PodcastViewModel> = .loading\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"h2\", null, \"Updating the PodcastManagedObject\"), mdx(\"p\", null, \"We need to convert the episodes set to a swift array to be used comfortably. \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-PodcastManagedObject\\n\\nimport Foundation\\nimport CoreData\\n\\nextension PodcastManagedObject {\\n\\n    @nonobjc public class func fetchRequest() -> NSFetchRequest<PodcastManagedObject> {\\n        return NSFetchRequest<PodcastManagedObject>(entityName: \\\"PodcastManagedObject\\\")\\n    }\\n\\n    @NSManaged public var author: String?\\n    @NSManaged public var country: String?\\n    @NSManaged public var createdAt: Date?\\n    @NSManaged public var descr: String?\\n    @NSManaged public var explicitContent: String?\\n    @NSManaged public var feedUrl: String?\\n    @NSManaged public var genres: String?\\n    @NSManaged public var id: Int64\\n    @NSManaged public var image: Data?\\n    @NSManaged public var imageUrl: String?\\n    @NSManaged public var isBookmarked: Bool\\n    @NSManaged public var language: String?\\n    @NSManaged public var releaseDate: Date?\\n    @NSManaged public var thumbnail: Data?\\n    @NSManaged public var thumbnailUrl: String?\\n    @NSManaged public var title: String?\\n    @NSManaged public var totalEpisodes: Int64\\n    @NSManaged public var episodes: NSSet?\\n\\n    // highlight-start\\n    public var episodesArray: [EpisodeManagedObject] {\\n        guard let episodeSet = episodes as?  Set<EpisodeManagedObject> else {\\n            return []\\n        }\\n\\n        return episodeSet.sorted {\\n            $0.createdAt! > $1.createdAt!\\n        }\\n    }\\n    // highlight-end\\n\\n}\\n\")), mdx(\"h2\", null, \"Updating the PodcastViewModel\"), mdx(\"p\", null, \"Create another overload of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"init\"), \" method.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-PodcastViewModel\\n\\nimport Foundation\\nimport FeedKit\\n\\nstruct PodcastViewModel: Identifiable, Equatable {\\n\\n      ...\\n\\n    // highlight-start\\n    init(_ managedPodcast: PodcastManagedObject, existingPodcast: PodcastViewModel? = nil) {\\n        self.id =  Int(managedPodcast.id)\\n        self.title = managedPodcast.title ?? \\\"\\\"\\n        self.imageUrl = managedPodcast.imageUrl ?? \\\"\\\"\\n        self.author = managedPodcast.author ?? \\\"\\\"\\n        self.thumbnailUrl = managedPodcast.thumbnailUrl ?? \\\"\\\"\\n        self.image = managedPodcast.image ?? Data()\\n        self.thumbnail =  managedPodcast.thumbnail ?? Data()\\n        self.totalEpisodes =  managedPodcast.episodes?.count ?? 0\\n        self.explicitContent = managedPodcast.explicitContent ?? \\\"\\\"\\n        self.description = managedPodcast.descr ?? \\\"\\\"\\n        self.language = managedPodcast.language ?? \\\"\\\"\\n        self.country = managedPodcast.country ?? \\\"\\\"\\n                self.publisher = managedPodcast.publisher ?? \\\"\\\"\\n        self.genres = managedPodcast.genres?.components(separatedBy: \\\", \\\") ?? []\\n        self.nextEpisodePubDate = Date()\\n        self.feedUrl = managedPodcast.feedUrl ?? \\\"\\\"\\n        self.episodes = existingPodcast?.episodes ?? []\\n        self.isBookmarked = managedPodcast.isBookmarked\\n    }\\n    // highlight-end\\n\\n        ...\\n\\n    static func == (lhs: PodcastViewModel, rhs: PodcastViewModel) -> Bool {\\n        return lhs.id == rhs.id\\n            && lhs.language == rhs.language\\n            && lhs.episodes.count == rhs.episodes.count\\n            && lhs.image.count == rhs.image.count\\n            && lhs.thumbnail.count == rhs.thumbnail.count\\n    }\\n}\\n\")), mdx(\"h2\", null, \"Updating the EpisodeViewModel\"), mdx(\"p\", null, \"First replace the existing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"id\"), \" declaration, and create another overload of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"init\"), \" method \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-EpisodeViewModel\\n\\nimport Foundation\\nimport FeedKit\\n\\nstruct EpisodeViewModel: Identifiable {\\n\\n    // highlight-start\\n    var id = UUID()\\n    // highlight-end\\n    var title, description: String\\n    var date: String\\n    var audioUrl: String\\n    var audio: Data?\\n    var audioLength: Double\\n    var audioSize: String\\n    var podcast: PodcastViewModel = .placeholder\\n\\n        ...\\n\\n    // highlight-start\\n    init(downloaded: EpisodeManagedObject, podcast: PodcastViewModel) {\\n        self.id = downloaded.id ?? UUID()\\n        self.title = downloaded.title ?? \\\"\\\"\\n        self.description = downloaded.descr ?? \\\"\\\"\\n        self.date = downloaded.releaseDate?.format() ?? \\\"\\\"\\n        self.audioUrl = downloaded.sourceAudioUrl ?? \\\"\\\"\\n        self.audioLength = downloaded.duration\\n        self.audioSize = downloaded.size ?? \\\"\\\"\\n        self.audio = downloaded.audio\\n        self.podcast = podcast\\n    }\\n    // highlight-end\\n\\n    init(feedItem: RSSFeedItem, podcast: PodcastViewModel) {\\n        self.title = feedItem.title ?? \\\"\\\"\\n        self.description = feedItem.description ?? \\\"\\\"\\n        self.date = feedItem.pubDate.map { $0.format() } ?? \\\"\\\"\\n        self.audioUrl = feedItem.enclosure?.attributes?.url ?? \\\"\\\"\\n        self.audioLength = feedItem.iTunes?.iTunesDuration ?? 0\\n\\n        self.audioSize = feedItem.enclosure?.attributes?.length.map {\\n            \\\"\\\\($0 / 1000000) mb\\\"\\n        } ?? \\\"0 mb\\\"\\n\\n        self.podcast = podcast\\n    }\\n}\\n\")), mdx(\"p\", null, \"Now that we have created that overload initialiser in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeViewModel\"), \", let's now update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastViewModel\"), \".\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-PodcastViewModel\\n\\nimport Foundation\\nimport FeedKit\\n\\nstruct PodcastViewModel: Identifiable, Equatable {\\n\\n        ...\\n\\n    init(_ managedPodcast: PodcastManagedObject, existingPodcast: PodcastViewModel? = nil) {\\n        self.id =  Int(managedPodcast.id)\\n        self.title = managedPodcast.title ?? \\\"\\\"\\n        self.imageUrl = managedPodcast.imageUrl ?? \\\"\\\"\\n        self.author = managedPodcast.author ?? \\\"\\\"\\n        self.thumbnailUrl = managedPodcast.thumbnailUrl ?? \\\"\\\"\\n        self.image = managedPodcast.image ?? Data()\\n        self.thumbnail =  managedPodcast.thumbnail ?? Data()\\n        self.totalEpisodes =  managedPodcast.episodes?.count ?? 0\\n        self.explicitContent = managedPodcast.explicitContent ?? \\\"\\\"\\n        self.description = managedPodcast.descr ?? \\\"\\\"\\n        self.language = managedPodcast.language ?? \\\"\\\"\\n        self.country = managedPodcast.country ?? \\\"\\\"\\n        self.genres = managedPodcast.genres?.components(separatedBy: \\\", \\\") ?? []\\n        self.nextEpisodePubDate = Date()\\n        self.feedUrl = managedPodcast.feedUrl ?? \\\"\\\"\\n        // highlight-start\\n        self.episodes = existingPodcast?.episodes.map({ episode  in\\n            if let managedEpisode = managedPodcast.episodesArray.first(where: { $0.title == episode.title }){\\n                return EpisodeViewModel(downloaded: managedEpisode, podcast: self)\\n            } else {\\n                return episode\\n            }\\n        }) ?? []\\n        // highlight-end\\n        self.isBookmarked = managedPodcast.isBookmarked\\n    }\\n\\n    ...\\n}\\n\")), mdx(\"p\", null, \"We are looping over the existing podcast's episodes array, and checking whether each of them exist in the managed podcast's episodes array. If they do, we create and return a new updated episode, otherwise we return the existing episode\"), mdx(\"h2\", null, \"Updating the Coredata reducer\"), mdx(\"p\", null, \"As always let's make some change in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CoredataReducer\"), \" struct. \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-CoredataReducer\\n\\nimport Foundation\\n\\nstruct CoredataReducer {\\n\\n    static func execute(action: Action.Coredata, state: inout AppState.Coredata, environment: AppEnvironment) {\\n\\n        switch action {\\n\\n        case .fetchGenres:\\n            state.savedGenres = environment.coredata.fetchGenres()\\n        case .saveGenres(let genres):\\n            _ = environment.coredata.saveGenres(genres)\\n        case .fetchPodcast(let podcast):\\n        // highlight-start\\n            if let managedPodcast = environment.coredata.fetchPodcast(podcast.id) {\\n                let podcast = PodcastViewModel(managedPodcast, existingPodcast: podcast)\\n                state.savedPodcast = .success(podcast)\\n            } else {\\n                state.savedPodcast = .failure\\n            }            \\n        // highlight-end\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"So here, we are fetching a podcast given its id, then update the state appropriately.\"), mdx(\"h2\", null, \"Updating the PodcastDetailScreen\"), mdx(\"p\", null, \"Beside observing changes for the api's podcast state, we also want to watch for the changes to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"coredata\"), \" \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"savedPodcast\"), \".\"), mdx(\"p\", null, \"We replace the state update line with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"checkCoredataState\"), \" method which will eventually update the state.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-PodcastDetailScreen\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailScreen: View {\\n\\n        ...\\n\\n    var body: some View {\\n\\n        ZStack {\\n                    ...\\n        }.onAppear { \\n            checkState()\\n        }.onChange(of: store.apiState.podcast) { _ in\\n            checkState()\\n                // highlight-start\\n        }.onChange(of: store.coredataState.savedPodcast) { _ in\\n            checkState()\\n        }\\n                // highlight-start\\n    }\\n\\n    private func checkState() {\\n        if case .success(let podcast) = store.apiState.podcast,\\n           podcast.title == self.podcast.title {\\n            // highlight-start\\n            checkCoredataState(for: podcast)\\n            // highlight-end\\n        } else {\\n            store.dispatch(.api(.updatePodcast(podcast)))\\n        }\\n    }\\n\\n    // highlight-start\\n    private func checkCoredataState(for podcast: PodcastViewModel) {\\n        switch store.coredataState.savedPodcast {\\n        case .loading:\\n            break\\n        case .success(_):\\n            break\\n        case .failure:\\n            break\\n        }\\n    }\\n    // highlight-end\\n}\\n\\n        ...\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-PodcastDetailScreen\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailScreen: View {\\n\\n        ...\\n\\n    var body: some View {\\n\\n        ZStack {\\n           ...\\n        }.onAppear { \\n            checkState()\\n        }.onChange(of: store.apiState.podcast) { _ in\\n            checkState()\\n        }.onChange(of: store.coredataState.savedPodcast) { _ in\\n            checkState()\\n        }\\n    }\\n\\n    private func checkState() {\\n        if case .success(let podcast) = store.apiState.podcast,\\n           podcast.title == self.podcast.title {\\n            checkCoredataState(for: podcast)\\n        } else {\\n            store.dispatch(.api(.updatePodcast(podcast)))\\n        }\\n    }\\n\\n    private func checkCoredataState(for podcast: PodcastViewModel) {\\n        switch store.coredataState.savedPodcast {\\n        case .loading:\\n            // highlight-start\\n            store.dispatch(.coredata(.fetchPodcast(podcast)))\\n            // highlight-end\\n        case .success(_):\\n            break\\n        case .failure:\\n            // highlight-start\\n            state = store.apiState.podcast\\n        // highlight-end\\n        }\\n    }\\n}\\n\\n    ...\\n\")), mdx(\"p\", null, \"So, the deal here is that we fetch the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"coredata\"), \" \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" for a particular podcast if the state is loading which basically means it's unknown.\"), mdx(\"p\", null, \"If the state is a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"failure\"), \", it means there's no saved version for this podcast, so we just set it to the existing api state.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-PodcastDetailScreen\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailScreen: View {\\n\\n        ...\\n\\n    var body: some View {\\n\\n        ZStack {\\n            ...\\n        }.onAppear { \\n            checkState()\\n        }.onChange(of: store.apiState.podcast) { _ in\\n            checkState()\\n        }.onChange(of: store.coredataState.savedPodcast) { _ in\\n            checkState()\\n        }\\n    }\\n\\n    private func checkState() {\\n        if case .success(let podcast) = store.apiState.podcast,\\n           podcast.title == self.podcast.title {\\n            checkCoredataState(for: podcast)\\n        } else {\\n            store.dispatch(.api(.updatePodcast(podcast)))\\n            Log.info(\\\"Dispatching update podcast\\\")\\n        }\\n    }\\n\\n    private func checkCoredataState(for podcast: PodcastViewModel) {\\n        switch store.coredataState.savedPodcast {\\n        case .loading:\\n            store.dispatch(.coredata(.fetchPodcast(podcast)))\\n        case .success(let savedPodcast):\\n            // highlight-start\\n            if savedPodcast.title == self.podcast.title {\\n                state = store.coredataState.savedPodcast\\n            } else {\\n                store.dispatch(.coredata(.fetchPodcast(podcast)))\\n            }\\n            // highlight-end\\n        case .failure:\\n            state = store.apiState.podcast\\n        }\\n    }\\n}\\n\\n    ...\\n\")), mdx(\"p\", null, \"If we have a valid podcast in the global coredata state which is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"savedPodcast\"), \", we check whether it's the same as what has been clicked by the user, then set it to the local state, otherwise we dispatch another action to update to the current podcast.\"), mdx(\"p\", null, \"And this is the end of lesson #21, see you in #22 where we will start working on the bookmarking feature. Happy coding guys!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#circularprogressview","title":"CircularProgressView"},{"url":"#circularprogressviewstyle","title":"CircularProgressViewStyle"},{"url":"#updating-the-episodeviewheader","title":"Updating the EpisodeViewHeader"},{"url":"#updating-the-action","title":"Updating the Action"},{"url":"#updating-the-appstate","title":"Updating the AppState"},{"url":"#updating-the-podcastmanagedobject","title":"Updating the PodcastManagedObject"},{"url":"#updating-the-podcastviewmodel","title":"Updating the PodcastViewModel"},{"url":"#updating-the-episodeviewmodel","title":"Updating the EpisodeViewModel"},{"url":"#updating-the-coredata-reducer","title":"Updating the Coredata reducer"},{"url":"#updating-the-podcastdetailscreen","title":"Updating the PodcastDetailScreen"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-03-19T09:20:45.873Z","isNewLayout":true,"previews":{"previews":"{\n\t\"Lesson22UpdateEpisodeListScreen3\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1615632365/StreamCast/Lesson%2022/wen3yhejufhzug40uokl.gif\"]\n}"},"slug":"lesson-22-bookmarking","title":"Lesson #22: Bookmarking Part 1","updatedDate":"2021-03-19T00:00+00:00","downloads":[{"url":"https://icedrive.net/0/51XU35ohAY","type":"zip","id":"be4ed826-1c76-5abd-bb46-4b888010d068","name":"Source code"}],"allChapters":null,"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson22UpdateEpisodeListScreen1\\n// $$liquidcoderFilename-EpisodeListScreen\\n\\nimport SwiftUI\\n\\nstruct EpisodeListScreen: View {\\n\\n    var episodes: [EpisodeViewModel] = []\\n\\n    enum ListSection: String, CaseIterable {\\n        case downloaded = \\\"Downloaded\\\"\\n        case allEpisodes = \\\"All episodes\\\"\\n    }\\n\\n    @State private var listSection = ListSection.allEpisodes\\n\\n    // highlight-start\\n    @State private var filteredEpisodes = [EpisodeViewModel]()\\n    // highlight-end\\n\\n    private var section: some View {\\n\\n        Section(header: createSegmentedPicker()) {\\n            // highlight-start\\n            ForEach(filteredEpisodes) { episode in\\n            // highlight-end\\n                EpisodeView(episode: episode, play: {})\\n            }\\n        }\\n    }\\n\\n    var body: some View {\\n\\n        ScrollView {\\n            LazyVGrid(columns: GridItems.shared,\\n                      alignment: .leading,\\n                      spacing: 20,\\n                      pinnedViews: [.sectionHeaders],\\n                      content: { section }\\n            ).padding()\\n        }\\n    }\\n\\n    func createSegmentedPicker() -> some View {\\n        Picker(\\\"\\\", selection: $listSection) {\\n            Text(ListSection.allEpisodes.rawValue)\\n                .tag(ListSection.allEpisodes)\\n            Text(ListSection.downloaded.rawValue)\\n                .tag(ListSection.downloaded)\\n        }.pickerStyle(SegmentedPickerStyle())\\n        .padding(.vertical, 5)\\n        .background(Color.background)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson22UpdateEpisodeListScreen2\\n// $$liquidcoderFilename-EpisodeListScreen\\n\\nimport SwiftUI\\n\\nstruct EpisodeListScreen: View {\\n\\n    var episodes: [EpisodeViewModel] = []\\n\\n    enum ListSection: String, CaseIterable {\\n        case downloaded = \\\"Downloaded\\\"\\n        case allEpisodes = \\\"All episodes\\\"\\n    }\\n\\n    @State private var listSection = ListSection.allEpisodes\\n\\n    @State private var filteredEpisodes = [EpisodeViewModel]()\\n\\n    private var section: some View {\\n\\n        Section(header: createSegmentedPicker()) {\\n            ForEach(filteredEpisodes) { episode in\\n                EpisodeView(episode: episode, play: {})\\n            }\\n        }\\n    }\\n\\n    var body: some View {\\n\\n        ScrollView {\\n            LazyVGrid(columns: GridItems.shared,\\n                      alignment: .leading,\\n                      spacing: 20,\\n                      pinnedViews: [.sectionHeaders],\\n                      content: { section }\\n            ).padding()\\n        // highlight-start\\n        }.onAppear(perform: filterEpisodes)\\n        .onChange(of: listSection) { _ in\\n            filterEpisodes()\\n        }\\n        // highlight-end\\n    }\\n\\n    // highlight-start\\n    fileprivate func filterEpisodes() {\\n\\n        switch listSection {\\n        case .downloaded:\\n            filteredEpisodes = episodes.filter({  episode in\\n                if let audio = episode.audio {\\n                    return !audio.isEmpty\\n                }\\n                return false\\n            })\\n        case .allEpisodes:\\n            filteredEpisodes = episodes\\n        }\\n    }\\n    // highlight-end\\n\\n    func createSegmentedPicker() -> some View {\\n        Picker(\\\"\\\", selection: $listSection) {\\n            Text(ListSection.allEpisodes.rawValue)\\n                .tag(ListSection.allEpisodes)\\n            Text(ListSection.downloaded.rawValue)\\n                .tag(ListSection.downloaded)\\n        }.pickerStyle(SegmentedPickerStyle())\\n        .padding(.vertical, 5)\\n        .background(Color.background)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson22UpdateEpisodeListScreen3\\n// $$liquidcoderFilename-EpisodeListScreen\\n\\nimport SwiftUI\\n\\nstruct EpisodeListScreen: View {\\n\\n    var episodes: [EpisodeViewModel] = []\\n\\n    enum ListSection: String, CaseIterable {\\n        case downloaded = \\\"Downloaded\\\"\\n        case allEpisodes = \\\"All episodes\\\"\\n    }\\n\\n    @State private var listSection = ListSection.allEpisodes\\n\\n    @State private var filteredEpisodes = [EpisodeViewModel]()\\n\\n    private var section: some View {\\n\\n        Section(header: createSegmentedPicker()) {\\n            ForEach(filteredEpisodes) { episode in\\n                EpisodeView(episode: episode, play: {})\\n                    // highlight-start\\n                    .transition(.move(edge: .leading))\\n                    // highlight-end\\n            }\\n        }\\n    }\\n\\n    var body: some View {\\n\\n        ScrollView {\\n            LazyVGrid(columns: GridItems.shared,\\n                      alignment: .leading,\\n                      spacing: 20,\\n                      pinnedViews: [.sectionHeaders],\\n                      content: { section }\\n            ).padding()\\n\\n        }.onAppear(perform: filterEpisodes)\\n        .onChange(of: listSection) { _ in\\n            filterEpisodes()\\n        // highlight-start\\n        }.animation(.easeIn, value: listSection)\\n        // highlight-end\\n    }\\n\\n    fileprivate func filterEpisodes() {\\n\\n        switch listSection {\\n        case .downloaded:\\n            filteredEpisodes = episodes.filter({  episode in\\n                if let audio = episode.audio {\\n                    return !audio.isEmpty\\n                }\\n                return false\\n            })\\n        case .allEpisodes:\\n            filteredEpisodes = episodes\\n        }\\n    }\\n\\n    func createSegmentedPicker() -> some View {\\n        Picker(\\\"\\\", selection: $listSection) {\\n            Text(ListSection.allEpisodes.rawValue)\\n                .tag(ListSection.allEpisodes)\\n            Text(ListSection.downloaded.rawValue)\\n                .tag(ListSection.downloaded)\\n        }.pickerStyle(SegmentedPickerStyle())\\n        .padding(.vertical, 5)\\n        .background(Color.background)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson22CoredataServiceProtocol1\\n// $$liquidcoderFilename-CoredataServiceProtocol\\n\\nimport Foundation\\n\\nprotocol CoredataServiceProtocol {\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool\\n    func fetchGenres() -> [GenreViewModel]\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool\\n    func fetchEpisode(_ episode: EpisodeViewModel) -> EpisodeManagedObject?\\n    func fetchPodcast(_ id: Int) -> PodcastManagedObject?\\n    // highlight-start\\n    func bookmarkPodcast(_ podcast: PodcastViewModel) -> PodcastManagedObject?\\n    func unbookmarkedPodcast(_ id: Int) -> PodcastManagedObject?\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson22UpdatePreviewCoredataService1\\n// $$liquidcoderFilename-PreviewCoredataService\\n\\nimport CoreData\\n\\nclass PreviewCoredataService: CoredataServiceProtocol {\\n\\n    // highlight-start\\n    func bookmarkPodcast(_ podcast: PodcastViewModel) -> PodcastManagedObject? {\\n        return nil\\n    }\\n\\n    func unbookmarkedPodcast(_ id: Int) -> PodcastManagedObject? {\\n        return nil\\n    }\\n    // highlight-end\\n\\n    func fetchEpisode(_ episode: EpisodeViewModel) -> EpisodeManagedObject? {\\n        return nil\\n    }\\n\\n    func fetchPodcast(_ id: Int) -> PodcastManagedObject? {\\n        return nil\\n    }\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool {\\n\\n        return true\\n    }\\n\\n    func fetchGenres() -> [GenreViewModel] {\\n        return GenreViewModel.listPlaceholders\\n    }\\n\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool {\\n        return false\\n    }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson22UpdateCoredataService1\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n    private var context: NSManagedObjectContext {\\n        return PersistenceController.shared.container.viewContext\\n    }\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool {\\n        genres.forEach {\\n            let newGenre = GenreManagedObject(context: context)\\n            newGenre.id = Int64($0.id)\\n            newGenre.name = $0.name\\n        }\\n        return saveContext()\\n    }\\n\\n    func fetchGenres() -> [GenreViewModel] {\\n        let request: NSFetchRequest<GenreManagedObject> = GenreManagedObject.fetchRequest()\\n\\n        do {\\n            let results = try context.fetch(request)\\n            return results.map { GenreViewModel(genre: $0) }\\n        } catch let error {\\n            Log.error(error)\\n            let nsError = error as NSError\\n            fatalError(\\\"Unresolved error \\\\(nsError), \\\\(nsError.userInfo)\\\")\\n        }\\n    }\\n\\n    // highlight-start\\n    func bookmarkPodcast(_ podcast: PodcastViewModel) -> Bool {\\n\\n    }\\n\\n    func unbookmarkedPodcast(_ id: Int) -> Bool {\\n\\n    }\\n    // highlight-end\\n\\n    ...\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson22UpdateCoredataService2\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n    ...\\n\\n    func bookmarkPodcast(_ podcast: PodcastViewModel) -> PodcastManagedObject? {\\n\\n        // highlight-start\\n        var managedPodcast: PodcastManagedObject?\\n\\n        if let existingPodcast = fetchPodcast(podcast.id) {\\n            existingPodcast.isBookmarked = true\\n            managedPodcast = existingPodcast\\n        } else {\\n            let newPodcast = createManagedPodcast(podcast)\\n            newPodcast.isBookmarked = true\\n            managedPodcast = newPodcast\\n        }\\n\\n        if saveContext() {\\n            return managedPodcast\\n        }\\n\\n        return nil\\n        // highlight-end\\n    }\\n\\n    func unbookmarkedPodcast(_ id: Int) -> PodcastManagedObject? {\\n\\n    }\\n\\n    ...\\n\\n    private func saveContext() -> Bool {\\n        do {\\n            if context.hasChanges {\\n                try context.save()\\n            }\\n        } catch {\\n            let nsError = error as NSError\\n            fatalError(\\\"Unresolved error \\\\(nsError), \\\\(nsError.userInfo)\\\")\\n        }\\n\\n        return true\\n    }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson22UpdateCoredataService3\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n        ...\\n\\n    func bookmarkPodcast(_ podcast: PodcastViewModel) -> PodcastManagedObject? {\\n\\n        ...\\n    }\\n\\n    func unbookmarkedPodcast(_ id: Int) -> PodcastManagedObject? {\\n\\n        // highlight-start\\n        guard let managedPodcast = fetchPodcast(id) else {\\n            return nil\\n        }\\n\\n        if managedPodcast.episodesArray.isEmpty {\\n            context.delete(managedPodcast)\\n            if saveContext() {\\n                return nil\\n            }\\n        }\\n\\n        managedPodcast.isBookmarked = false\\n        _ = saveContext()\\n\\n        return managedPodcast\\n        // highlight-end\\n\\n    }\\n\\n        ... \\n\\n    private func saveContext() -> Bool {\\n        do {\\n            if context.hasChanges {\\n                try context.save()\\n            }\\n        } catch {\\n            let nsError = error as NSError\\n            fatalError(\\\"Unresolved error \\\\(nsError), \\\\(nsError.userInfo)\\\")\\n        }\\n\\n        return true\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson22UpdateAction1\\n// $$liquidcoderFilename-Action\\n\\nimport Foundation\\n\\nenum Action {\\n    enum Api {\\n        case fetchPodcasts(_ term: String, limit: Int = 10)\\n        case fetchGenres(_ filename: String)\\n        case updatePodcast(_ podcast: PodcastViewModel)\\n    }\\n\\n    enum Coredata {\\n        case fetchGenres\\n        case saveGenres(_ genres: [GenreViewModel])\\n        case fetchPodcast(_ podcast: PodcastViewModel)\\n        // highlight-start\\n        case bookmark(_ podcast: PodcastViewModel)\\n        case unbookmarked(_ podcast: PodcastViewModel)\\n        // highlight-end\\n    }\\n\\n    case api(_ action: Api)\\n    case coredata(_ action: Coredata)\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson22UpdateCoredataReducer1\\n// $$liquidcoderFilename-CoredataReducer\\n\\nimport Foundation\\n\\nstruct CoredataReducer {\\n\\n    static func execute(action: Action.Coredata, state: inout AppState.Coredata, environment: AppEnvironment) {\\n\\n        switch action {\\n\\n        case .fetchGenres:\\n            state.savedGenres = environment.coredata.fetchGenres()\\n        case .saveGenres(let genres):\\n            _ = environment.coredata.saveGenres(genres)\\n        case .fetchPodcast(let podcast):\\n            // highlight-start\\n            let managedPodcast = environment.coredata.fetchPodcast(podcast.id)\\n            updateState(&state, savedPodcast: managedPodcast, existingPodcast: podcast)\\n            // highlight-end\\n        case .bookmark(let podcast):\\n            // highlight-start\\n            if let managedPodcast = environment.coredata.bookmarkPodcast(podcast) {\\n                updateState(&state, savedPodcast: managedPodcast, existingPodcast: podcast)\\n            }\\n            // highlight-end\\n        case .deleteBookmarked(let podcast):\\n            // highlight-start\\n            let managedPodcast = environment.coredata.deleteBookmarked(podcast.id)\\n            updateState(&state, savedPodcast: managedPodcast, existingPodcast: podcast)\\n            // highlight-end\\n        }\\n    }\\n\\n    // highlight-start\\n    private static func updateState(_ state: inout AppState.Coredata, savedPodcast: PodcastManagedObject?, existingPodcast: PodcastViewModel) {\\n        if let managedPodcast = savedPodcast {\\n            let podcast = PodcastViewModel(managedPodcast, existingPodcast: existingPodcast)\\n            state.savedPodcast = .success(podcast)\\n        } else {\\n            state.savedPodcast = .failure\\n        }\\n    }\\n    // highlight-start\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson22Intro-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey guys, welcome back to lesson #22. In the previous lesson, we finished working on the download feature, and in this one, we will focus on implementing the bookmarking feature. Let's get started.\")), mdx(\"div\", {\n    id: \"Lesson22UpdateEpisodeListScreen1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the EpisodeListScreen\"), mdx(\"p\", null, \"We want to allow  users to filter out downloaded episodes for a given podcast that's why we declare the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"filteredEpisodes\"), \" array that we then replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"episodes\"), \" with in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ForEach\"), \" view inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"section\"), \" view.\")), mdx(\"div\", {\n    id: \"Lesson22UpdateEpisodeListScreen2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We also want to filter out episodes whenever the view appears as well as when the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"listSection\"), \" property changes.\"), mdx(\"hr\", null), mdx(\"p\", null, \"Inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"filterEpisodes\"), \" function, we switch on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"listSection\"), \", and in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"downloaded\"), \" case block, we will filter out episodes where the audio data is not empty which means that they have been downloaded. For \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"allEpisodes\"), \", we just set the entire array of episodes to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"filteredEpisodes\"), \" one.\")), mdx(\"div\", {\n    id: \"Lesson22UpdateEpisodeListScreen3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We add a little bit of animation when we switch sections. We want the items to move in from the left and to animate only when the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"listSection\"), \" property changes that's why we are using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"animate\"), \" with value.\")), mdx(\"div\", {\n    id: \"Lesson22CoredataServiceProtocol1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the coredata service protocol\"), mdx(\"p\", null, \"We need to update the coredata service in order to implement bookmarking starting with the protocol as always.\")), mdx(\"div\", {\n    id: \"Lesson22UpdatePreviewCoredataService1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the preview coredata service\"), mdx(\"p\", null, \"Updating the preview protocol requires us to also update its implementations, so add the following in the preview coredata service as well.\")), mdx(\"div\", {\n    id: \"Lesson22UpdateCoredataService1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the coredata service\"), mdx(\"p\", null, \"Let us now do the actual work in here. First add the 2 methods to silence the error.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Click \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Ctrl + Option + CMD + F\"), \" to fix the error in one go.\"))), mdx(\"div\", {\n    id: \"Lesson22UpdateCoredataService2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"To bookmark a podcast, we first need to check if the podcast is already saved, then we set its \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isBookmarked\"), \" to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"true\"), \", and return the newly bookmarked podcast, otherwise we need to create a new podcast, set its \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isBookmarked\"), \" to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"true\"), \" and return it.\"), mdx(\"p\", null, \"If we cannot save the context, we return nil.\")), mdx(\"div\", {\n    id: \"Lesson22UpdateCoredataService3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"To unbookmark a podcast, we  need to make sure that the podcast is at least saved in coredata, otherwise there's no need to proceed, we return nil.\"), mdx(\"p\", null, \"If the podcast is saved, we check if  it contains episodes, if that's not the case, we delete the entire object from coredata, otherwise we set \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isBookmarked\"), \" to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"false\"), \".\"), mdx(\"p\", null, \"Last, we saved the context and return the updated podcast.\")), mdx(\"div\", {\n    id: \"Lesson22UpdateAction1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the Action\"), mdx(\"p\", null, \"Add the following to allow us to dispatch bookmarking related actions.\")), mdx(\"div\", {\n    id: \"Lesson22UpdateCoredataReducer1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the CoredataReducer\"), mdx(\"p\", null, \"Don't worry about the amount of changes you are seeing, we just did a little bit of refactoring that's all.\"), mdx(\"hr\", null), mdx(\"p\", null, \"So, here we are updating the state with the updated podcast from the database. We use the returned \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastManagedObject\"), \" to create a new \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastViewModel\"), \" object that we update  the state with\")), mdx(\"div\", {\n    id: \"Lesson22Outro-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And this bring this short lesson #22 to a close. See you in lesson #23 where we will integrate this bookmarking feature into our views. See you then and happy coding!!!.\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#updating-the-episodelistscreen","title":"Updating the EpisodeListScreen"},{"url":"#updating-the-coredata-service-protocol","title":"Updating the coredata service protocol"},{"url":"#updating-the-preview-coredata-service","title":"Updating the preview coredata service"},{"url":"#updating-the-coredata-service","title":"Updating the coredata service"},{"url":"#updating-the-action","title":"Updating the Action"},{"url":"#updating-the-coredatareducer","title":"Updating the CoredataReducer"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey guys, welcome back to lesson #22. In the previous lesson, we finished working on the download feature, and in this one, we will focus on implementing the bookmarking feature. Let's get started.\"), mdx(\"h2\", null, \"Updating the EpisodeListScreen\"), mdx(\"p\", null, \"We want to allow  users to filter out downloaded episodes for a given podcast that's why we declare the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"filteredEpisodes\"), \" array that we then replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"episodes\"), \" with in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ForEach\"), \" view inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"section\"), \" view.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateEpisodeListScreen1\\n// $$liquidcoderFilename-EpisodeListScreen\\n\\nimport SwiftUI\\n\\nstruct EpisodeListScreen: View {\\n\\n    var episodes: [EpisodeViewModel] = []\\n\\n    enum ListSection: String, CaseIterable {\\n        case downloaded = \\\"Downloaded\\\"\\n        case allEpisodes = \\\"All episodes\\\"\\n    }\\n\\n    @State private var listSection = ListSection.allEpisodes\\n\\n    // highlight-start\\n    @State private var filteredEpisodes = [EpisodeViewModel]()\\n    // highlight-end\\n\\n        private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n\\n    private var section: some View {\\n\\n        Section(header: createSegmentedPicker()) {\\n            // highlight-start\\n            ForEach(filteredEpisodes) { episode in\\n            // highlight-end\\n                EpisodeView(episode: episode, play: {})\\n            }\\n        }\\n    }\\n\\n    var body: some View {\\n\\n        ScrollView {\\n            LazyVGrid(columns: gridItems,\\n                      alignment: .leading,\\n                      spacing: 20,\\n                      pinnedViews: [.sectionHeaders],\\n                      content: { section }\\n            ).padding()\\n        }\\n    }\\n\\n    func createSegmentedPicker() -> some View {\\n        Picker(\\\"\\\", selection: $listSection) {\\n            Text(ListSection.allEpisodes.rawValue)\\n                .tag(ListSection.allEpisodes)\\n            Text(ListSection.downloaded.rawValue)\\n                .tag(ListSection.downloaded)\\n        }.pickerStyle(SegmentedPickerStyle())\\n        .padding(.vertical, 5)\\n        .background(Color.background)\\n    }\\n}\\n\")), mdx(\"p\", null, \"We also want to filter out episodes whenever the view appears as well as when the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"listSection\"), \" property changes.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateEpisodeListScreen2\\n// $$liquidcoderFilename-EpisodeListScreen\\n\\nimport SwiftUI\\n\\nstruct EpisodeListScreen: View {\\n\\n    var episodes: [EpisodeViewModel] = []\\n\\n    enum ListSection: String, CaseIterable {\\n        case downloaded = \\\"Downloaded\\\"\\n        case allEpisodes = \\\"All episodes\\\"\\n    }\\n\\n    @State private var listSection = ListSection.allEpisodes\\n\\n    @State private var filteredEpisodes = [EpisodeViewModel]()\\n\\n    private var section: some View {\\n\\n        Section(header: createSegmentedPicker()) {\\n            ForEach(filteredEpisodes) { episode in\\n                EpisodeView(episode: episode, play: {})\\n            }\\n        }\\n    }\\n\\n    var body: some View {\\n\\n        ScrollView {\\n            LazyVGrid(columns: GridItems.shared,\\n                      alignment: .leading,\\n                      spacing: 20,\\n                      pinnedViews: [.sectionHeaders],\\n                      content: { section }\\n            ).padding()\\n        // highlight-start\\n        }.onAppear(perform: filterEpisodes)\\n        .onChange(of: listSection) { _ in\\n            filterEpisodes()\\n        }\\n        // highlight-end\\n    }\\n\\n    // highlight-start\\n    fileprivate func filterEpisodes() {\\n\\n        switch listSection {\\n        case .downloaded:\\n            filteredEpisodes = episodes.filter({  episode in\\n                if let audio = episode.audio {\\n                    return !audio.isEmpty\\n                }\\n                return false\\n            })\\n        case .allEpisodes:\\n            filteredEpisodes = episodes\\n        }\\n    }\\n    // highlight-end\\n\\n    func createSegmentedPicker() -> some View {\\n        Picker(\\\"\\\", selection: $listSection) {\\n            Text(ListSection.allEpisodes.rawValue)\\n                .tag(ListSection.allEpisodes)\\n            Text(ListSection.downloaded.rawValue)\\n                .tag(ListSection.downloaded)\\n        }.pickerStyle(SegmentedPickerStyle())\\n        .padding(.vertical, 5)\\n        .background(Color.background)\\n    }\\n}\\n\")), mdx(\"p\", null, \"Inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"filterEpisodes\"), \" function, we switch on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"listSection\"), \", and in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"downloaded\"), \" case block, we will filter out episodes where the audio data is not empty which means that they have been downloaded. For \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"allEpisodes\"), \", we just set the entire array of episodes to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"filteredEpisodes\"), \" one.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderFilename-EpisodeListScreen\\n\\nimport SwiftUI\\n\\nstruct EpisodeListScreen: View {\\n\\n    var episodes: [EpisodeViewModel] = []\\n\\n    enum ListSection: String, CaseIterable {\\n        case downloaded = \\\"Downloaded\\\"\\n        case allEpisodes = \\\"All episodes\\\"\\n    }\\n\\n    @State private var listSection = ListSection.allEpisodes\\n\\n    @State private var filteredEpisodes = [EpisodeViewModel]()\\n\\n    private var section: some View {\\n\\n        Section(header: createSegmentedPicker()) {\\n            ForEach(filteredEpisodes) { episode in\\n                EpisodeView(episode: episode, play: {})\\n                    // highlight-start\\n                    .transition(.move(edge: .leading))\\n                    // highlight-end\\n            }\\n        }\\n    }\\n\\n    var body: some View {\\n\\n        ScrollView {\\n            LazyVGrid(columns: GridItems.shared,\\n                      alignment: .leading,\\n                      spacing: 20,\\n                      pinnedViews: [.sectionHeaders],\\n                      content: { section }\\n            ).padding()\\n\\n        }.onAppear(perform: filterEpisodes)\\n        .onChange(of: listSection) { _ in\\n            filterEpisodes()\\n        // highlight-start\\n        }.animation(.easeIn, value: listSection)\\n        // highlight-end\\n    }\\n\\n    fileprivate func filterEpisodes() {\\n\\n        switch listSection {\\n        case .downloaded:\\n            filteredEpisodes = episodes.filter({  episode in\\n                if let audio = episode.audio {\\n                    return !audio.isEmpty\\n                }\\n                return false\\n            })\\n        case .allEpisodes:\\n            filteredEpisodes = episodes\\n        }\\n    }\\n\\n    func createSegmentedPicker() -> some View {\\n        Picker(\\\"\\\", selection: $listSection) {\\n            Text(ListSection.allEpisodes.rawValue)\\n                .tag(ListSection.allEpisodes)\\n            Text(ListSection.downloaded.rawValue)\\n                .tag(ListSection.downloaded)\\n        }.pickerStyle(SegmentedPickerStyle())\\n        .padding(.vertical, 5)\\n        .background(Color.background)\\n    }\\n}\\n\")), mdx(\"p\", null, \"We add a little bit of animation when we switch sections. We want the items to move in from the left and to animate only when the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"listSection\"), \" property changes that's why we are using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"animate\"), \" with value.\"), mdx(\"h2\", null, \"Updating the coredata service protocol\"), mdx(\"p\", null, \"We need to update the coredata service in order to implement bookmarking starting with the protocol as always.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20CoredataServiceProtocol1\\n// $$liquidcoderFilename-CoredataServiceProtocol\\n\\nimport Foundation\\n\\nprotocol CoredataServiceProtocol {\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool\\n    func fetchGenres() -> [GenreViewModel]\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool\\n    func fetchEpisode(_ episode: EpisodeViewModel) -> EpisodeManagedObject?\\n    func fetchPodcast(_ id: Int) -> PodcastManagedObject?\\n    // highlight-start\\n    func bookmarkPodcast(_ podcast: PodcastViewModel) -> PodcastManagedObject?\\n    func unbookmarkedPodcast(_ id: Int) -> PodcastManagedObject?\\n    // highlight-end\\n}\\n\")), mdx(\"h2\", null, \"Updating the preview coredata service\"), mdx(\"p\", null, \"Updating the preview protocol requires us to also update its implementations, so add the following in the preview coredata service as well.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdatePreviewCoredataService1\\n// $$liquidcoderFilename-PreviewCoredataService\\n\\nimport CoreData\\n\\nclass PreviewCoredataService: CoredataServiceProtocol {\\n\\n    // highlight-start\\n    func bookmarkPodcast(_ podcast: PodcastViewModel) -> PodcastManagedObject? {\\n        return nil\\n    }\\n\\n    func unbookmarkedPodcast(_ id: Int) -> PodcastManagedObject? {\\n        return nil\\n    }\\n    // highlight-end\\n\\n    func fetchEpisode(_ episode: EpisodeViewModel) -> EpisodeManagedObject? {\\n        return nil\\n    }\\n\\n    func fetchPodcast(_ id: Int) -> PodcastManagedObject? {\\n        return nil\\n    }\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool {\\n\\n        return true\\n    }\\n\\n    func fetchGenres() -> [GenreViewModel] {\\n        return GenreViewModel.listPlaceholders\\n    }\\n\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool {\\n        return false\\n    }\\n\\n}\\n\")), mdx(\"h2\", null, \"Updating the coredata service\"), mdx(\"p\", null, \"Let us now do the actual work in here. First add the 2 methods to silence the error.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Click \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Ctrl + Option + CMD + F\"), \" to fix the error in one go.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateCoredataService1\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n    private var context: NSManagedObjectContext {\\n        return PersistenceController.shared.container.viewContext\\n    }\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool {\\n        genres.forEach {\\n            let newGenre = GenreManagedObject(context: context)\\n            newGenre.id = Int64($0.id)\\n            newGenre.name = $0.name\\n        }\\n        return saveContext()\\n    }\\n\\n    func fetchGenres() -> [GenreViewModel] {\\n        let request: NSFetchRequest<GenreManagedObject> = GenreManagedObject.fetchRequest()\\n\\n        do {\\n            let results = try context.fetch(request)\\n            return results.map { GenreViewModel(genre: $0) }\\n        } catch let error {\\n            Log.error(error)\\n            let nsError = error as NSError\\n            fatalError(\\\"Unresolved error \\\\(nsError), \\\\(nsError.userInfo)\\\")\\n        }\\n    }\\n\\n    // highlight-start\\n    func bookmarkPodcast(_ podcast: PodcastViewModel) -> Bool {\\n\\n    }\\n\\n    func unbookmarkedPodcast(_ id: Int) -> Bool {\\n\\n    }\\n    // highlight-end\\n\\n    ...\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateCoredataService2\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n    ...\\n\\n    func bookmarkPodcast(_ podcast: PodcastViewModel) -> PodcastManagedObject? {\\n\\n        // highlight-start\\n        var managedPodcast: PodcastManagedObject?\\n\\n        if let existingPodcast = fetchPodcast(podcast.id) {\\n            existingPodcast.isBookmarked = true\\n            managedPodcast = existingPodcast\\n        } else {\\n            let newPodcast = createManagedPodcast(podcast)\\n            newPodcast.isBookmarked = true\\n            managedPodcast = newPodcast\\n        }\\n\\n        if saveContext() {\\n            return managedPodcast\\n        }\\n\\n        return nil\\n        // highlight-end\\n    }\\n\\n    func unbookmarkedPodcast(_ id: Int) -> PodcastManagedObject? {\\n\\n    }\\n\\n    ...\\n\\n    private func saveContext() -> Bool {\\n        do {\\n            if context.hasChanges {\\n                try context.save()\\n            }\\n        } catch {\\n            let nsError = error as NSError\\n            fatalError(\\\"Unresolved error \\\\(nsError), \\\\(nsError.userInfo)\\\")\\n        }\\n\\n        return true\\n    }\\n\\n}\\n\")), mdx(\"p\", null, \"To bookmark a podcast, we first need to check if the podcast is already saved, then we set its \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isBookmarked\"), \" to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"true\"), \", and return the newly bookmarked podcast, otherwise we need to create a new podcast, set its \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isBookmarked\"), \" to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"true\"), \" and return it.\"), mdx(\"p\", null, \"If we cannot save the context, we return nil.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateCoredataService3\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n        ...\\n\\n    func bookmarkPodcast(_ podcast: PodcastViewModel) -> PodcastManagedObject? {\\n\\n        ...\\n    }\\n\\n    func unbookmarkedPodcast(_ id: Int) -> PodcastManagedObject? {\\n\\n        // highlight-start\\n        guard let managedPodcast = fetchPodcast(id) else {\\n            return nil\\n        }\\n\\n        if managedPodcast.episodesArray.isEmpty {\\n            context.delete(managedPodcast)\\n            if saveContext() {\\n                return nil\\n            }\\n        }\\n\\n        managedPodcast.isBookmarked = false\\n        _ = saveContext()\\n\\n        return managedPodcast\\n        // highlight-end\\n\\n    }\\n\\n        ... \\n\\n    private func saveContext() -> Bool {\\n        do {\\n            if context.hasChanges {\\n                try context.save()\\n            }\\n        } catch {\\n            let nsError = error as NSError\\n            fatalError(\\\"Unresolved error \\\\(nsError), \\\\(nsError.userInfo)\\\")\\n        }\\n\\n        return true\\n    }\\n}\\n\")), mdx(\"p\", null, \"To unbookmark a podcast, we  need to make sure that the podcast is at least saved in coredata, otherwise there's no need to proceed, we return nil.\"), mdx(\"p\", null, \"If the podcast is saved, we check if  it contains episodes, if that's not the case, we delete the entire object from coredata, otherwise we set \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isBookmarked\"), \" to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"false\"), \".\"), mdx(\"p\", null, \"Last, we saved the context and return the updated podcast.\"), mdx(\"h2\", null, \"Updating the Action\"), mdx(\"p\", null, \"Add the following to allow us to dispatch bookmarking related actions.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson20UpdateAction1\\n// $$liquidcoderFilename-Action\\n\\nimport Foundation\\n\\nenum Action {\\n    enum Api {\\n        case fetchPodcasts(_ term: String, limit: Int = 10)\\n        case fetchGenres(_ filename: String)\\n        case updatePodcast(_ podcast: PodcastViewModel)\\n    }\\n\\n    enum Coredata {\\n        case fetchGenres\\n        case saveGenres(_ genres: [GenreViewModel])\\n        case fetchPodcast(_ podcast: PodcastViewModel)\\n        // highlight-start\\n        case bookmark(_ podcast: PodcastViewModel)\\n        case unbookmarked(_ podcast: PodcastViewModel)\\n        // highlight-end\\n    }\\n\\n    case api(_ action: Api)\\n    case coredata(_ action: Coredata)\\n\\n}\\n\")), mdx(\"h2\", null, \"Updating the CoredataReducer\"), mdx(\"p\", null, \"Don't worry about the amount of changes you are seeing, we just did a little bit of refactoring that's all.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson22UpdateCoredataReducer1\\n// $$liquidcoderFilename-CoredataReducer\\n\\nimport Foundation\\n\\nstruct CoredataReducer {\\n\\n    static func execute(action: Action.Coredata, state: inout AppState.Coredata, environment: AppEnvironment) {\\n\\n        switch action {\\n\\n        case .fetchGenres:\\n            state.savedGenres = environment.coredata.fetchGenres()\\n        case .saveGenres(let genres):\\n            _ = environment.coredata.saveGenres(genres)\\n        case .fetchPodcast(let podcast):\\n            // highlight-start\\n            let managedPodcast = environment.coredata.fetchPodcast(podcast.id)\\n            updateState(&state, savedPodcast: managedPodcast, existingPodcast: podcast)\\n            // highlight-end\\n        case .bookmark(let podcast):\\n            // highlight-start\\n            if let managedPodcast = environment.coredata.bookmarkPodcast(podcast) {\\n                updateState(&state, savedPodcast: managedPodcast, existingPodcast: podcast)\\n            }\\n            // highlight-end\\n        case .unbookmarked(let podcast):\\n            // highlight-start\\n            let managedPodcast = environment.coredata.unbookmarkedPodcast(podcast.id)\\n            updateState(&state, savedPodcast: managedPodcast, existingPodcast: podcast)\\n            // highlight-end\\n        }\\n    }\\n\\n    // highlight-start\\n    private static func updateState(_ state: inout AppState.Coredata, savedPodcast: PodcastManagedObject?, existingPodcast: PodcastViewModel) {\\n        if let managedPodcast = savedPodcast {\\n            let podcast = PodcastViewModel(managedPodcast, existingPodcast: existingPodcast)\\n            state.savedPodcast = .success(podcast)\\n        } else {\\n            state.savedPodcast = .failure\\n        }\\n    }\\n    // highlight-start\\n}\\n\")), mdx(\"p\", null, \"So, here we are updating the state with the updated podcast from the database. We use the returned \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastManagedObject\"), \" to create a new \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastViewModel\"), \" object that we update  the state with\"), mdx(\"p\", null, \"And this bring this short lesson #22 to a close. See you in lesson #23 where we will integrate this bookmarking feature into our views. See you then and happy coding!!!.\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#updating-the-episodelistscreen","title":"Updating the EpisodeListScreen"},{"url":"#updating-the-coredata-service-protocol","title":"Updating the coredata service protocol"},{"url":"#updating-the-preview-coredata-service","title":"Updating the preview coredata service"},{"url":"#updating-the-coredata-service","title":"Updating the coredata service"},{"url":"#updating-the-action","title":"Updating the Action"},{"url":"#updating-the-coredatareducer","title":"Updating the CoredataReducer"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-03-22T09:16:19.507Z","isNewLayout":true,"previews":{"previews":"{\n\n\"Lesson21UpdatePodcastDetailContent2\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1615635930/StreamCast/Lesson%2023/crgazzhzho7wzqa9lfk3.gif\"],\n\"Lesson23BookmarkedScreen3\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\"Lesson23BookmarkedScreen4\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\"Lesson23BookmarkedScreen5\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1615636603/StreamCast/Lesson%2023/zvxqxamtwb8hntkj1fs0.png\"]\n}"},"slug":"lesson-23-bookmarking-part-2","title":"Lesson #23: Bookmarking Part 2","updatedDate":"2021-03-22T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZWIH2XZ0gNqxFbc6vJoMteuT65GQFw5MpfX","type":"zip","id":"1f830469-3c86-58a9-8f36-0949dcdcdb32","name":"Source code"}],"allChapters":null,"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23UpdatePodcastDetailContent1\\n// $$liquidcoderFilename-PodcastDetailContent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailContent: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    ...\\n\\n    // highlight-start\\n    private var bookmarkButton: some View {\\n        return EmptyView()\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        GeometryReader { geometry in\\n            ScrollView(showsIndicators: false, content: {\\n                VStack {\\n                    createImage(size: geometry.size.width)\\n\\n                    VStack(alignment: .leading, spacing: 10) {\\n                        PodcastDetailHeaderComponent(podcast: podcast)\\n                        allEpisodeLink\\n                        Divider()\\n                        description\\n\\n                    }\\n                }\\n            }).navigationBarTitle(\\\"\\\", displayMode: .inline)\\n            // highlight-start\\n            .navigationBarItems(trailing: bookmarkButton)\\n            // highlight-end\\n        }\\n    }\\n\\n    private func createImage(size: CGFloat) -> some View {\\n        SCImageView(podcast: podcast, size: size, cornerRadius: 0)\\n            .aspectRatio(contentMode: .fill)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23UpdatePodcastDetailContent2\\n// $$liquidcoderFilename-PodcastDetailContent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailContent: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    // highlight-start\\n    @EnvironmentObject private var store: Store\\n    // highlight-end\\n\\n    ...\\n\\n    private var bookmarkButton: some View {\\n        // highlight-start\\n        Button(action: {\\n            if podcast.isBookmarked {\\n                store.dispatch(.coredata(.unbookmarked(podcast)))\\n            } else {\\n                store.dispatch(.coredata(.bookmark(podcast)))\\n            }\\n        }, label: {\\n            Image(systemName:  podcast.isBookmarked ? \\\"heart.fill\\\" : \\\"heart\\\")\\n        })\\n        // highlight-end\\n    }\\n\\n    ...\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23UpdatePodcastViewModel1\\n// $$liquidcoderFilename-PodcastViewModel\\n\\nimport Foundation\\nimport FeedKit\\n\\nstruct PodcastViewModel: Identifiable, Equatable {\\n\\n    ...\\n\\n    static func == (lhs: PodcastViewModel, rhs: PodcastViewModel) -> Bool {\\n        return lhs.id == rhs.id\\n            && lhs.language == rhs.language\\n            && lhs.episodes.count == rhs.episodes.count\\n            && lhs.image.count == rhs.image.count\\n            && lhs.thumbnail.count == rhs.thumbnail.count\\n            // highlight-start\\n            && lhs.isBookmarked == rhs.isBookmarked\\n            // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23CoredataServiceProtocol1\\n// $$liquidcoderFilename-CoredataServiceProtocol\\n\\nimport Foundation\\n\\nprotocol CoredataServiceProtocol {\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool\\n    func fetchGenres() -> [GenreViewModel]\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool\\n    func fetchEpisode(_ episode: EpisodeViewModel) -> EpisodeManagedObject?\\n    func fetchPodcast(_ id: Int) -> PodcastManagedObject?\\n    func bookmarkPodcast(_ podcast: PodcastViewModel) -> PodcastManagedObject?\\n    func unbookmarkedPodcast(_ id: Int) -> PodcastManagedObject?\\n    // highlight-start\\n    func fetchAllManagedPodcasts(bookmarkedOnly: Bool) -> [PodcastViewModel]\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23UpdatePreviewCoredataService1\\n// $$liquidcoderFilename-PreviewCoredataService\\n\\nimport CoreData\\n\\nclass PreviewCoredataService: CoredataServiceProtocol {\\n\\n    // highlight-start\\n    func fetchAllManagedPodcasts(bookmarkedOnly: Bool) -> [PodcastViewModel] {\\n        return PodcastViewModel.listPlaceholder\\n    }\\n    // highlight-end\\n\\n    func bookmarkPodcast(_ podcast: PodcastViewModel) -> PodcastManagedObject? {\\n        return nil\\n    }\\n\\n    func unbookmarkedPodcast(_ id: Int) -> PodcastManagedObject? {\\n        return nil\\n    }\\n\\n    func fetchEpisode(_ episode: EpisodeViewModel) -> EpisodeManagedObject? {\\n        return nil\\n    }\\n\\n    func fetchPodcast(_ id: Int) -> PodcastManagedObject? {\\n        return nil\\n    }\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool {\\n\\n        return true\\n    }\\n\\n    func fetchGenres() -> [GenreViewModel] {\\n        return GenreViewModel.listPlaceholders\\n    }\\n\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool {\\n        return false\\n    }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23UpdateCoredataService1\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n    ...\\n\\n    func fetchGenres() -> [GenreViewModel] {\\n        let request: NSFetchRequest<GenreManagedObject> = GenreManagedObject.fetchRequest()\\n\\n        do {\\n            let results = try context.fetch(request)\\n            return results.map { GenreViewModel(genre: $0) }\\n        } catch let error {\\n            Log.error(error)\\n            let nsError = error as NSError\\n            fatalError(\\\"Unresolved error \\\\(nsError), \\\\(nsError.userInfo)\\\")\\n        }\\n    }\\n\\n    // highlight-start\\n    func fetchAllManagedPodcasts(bookmarkedOnly: Bool) -> [PodcastViewModel] {\\n\\n    }\\n    // highlight-end\\n\\n    ...\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23UpdateCoredataService2\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n    ...\\n\\n    func fetchGenres() -> [GenreViewModel] {\\n        let request: NSFetchRequest<GenreManagedObject> = GenreManagedObject.fetchRequest()\\n\\n        do {\\n            let results = try context.fetch(request)\\n            return results.map { GenreViewModel(genre: $0) }\\n        } catch let error {\\n            Log.error(error)\\n            let nsError = error as NSError\\n            fatalError(\\\"Unresolved error \\\\(nsError), \\\\(nsError.userInfo)\\\")\\n        }\\n    }\\n\\n    func fetchAllManagedPodcasts(bookmarkedOnly: Bool) -> [PodcastViewModel] {\\n        // highlight-start\\n        let request: NSFetchRequest<PodcastManagedObject> = PodcastManagedObject.fetchRequest()\\n\\n        if bookmarkedOnly {\\n            request.predicate = NSPredicate(format: \\\"isBookmarked = %@\\\", \\\"1\\\")\\n        }\\n\\n        do {\\n            let results = try context.fetch(request)\\n            return results.map { PodcastViewModel($0) }\\n        } catch let error {\\n            Log.error(error)\\n        }\\n\\n        return []\\n        // highlight-end\\n    }\\n\\n    ... \\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23UpdateAction1\\n// $$liquidcoderFilename-Action\\n\\nimport Foundation\\n\\nenum Action {\\n    enum Api {\\n        case fetchPodcasts(_ term: String, limit: Int = 10)\\n        case fetchGenres(_ filename: String)\\n        case updatePodcast(_ podcast: PodcastViewModel)\\n    }\\n\\n    enum Coredata {\\n        case fetchGenres\\n        case saveGenres(_ genres: [GenreViewModel])\\n        case fetchPodcast(_ podcast: PodcastViewModel)\\n        case bookmark(_ podcast: PodcastViewModel)\\n        case unbookmarked(_ podcast: PodcastViewModel)\\n        // highlight-start\\n        case fetchBookmarkedPodcasts\\n        // highlight-end\\n    }\\n\\n    case api(_ action: Api)\\n    case coredata(_ action: Coredata)\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23UpdateAppState1\\n// $$liquidcoderFilename-AppState\\n\\nimport Foundation\\nimport SwiftUI\\n\\nenum AppState {\\n\\n    enum Result<T: Equatable>: Equatable {\\n        static func == (lhs: AppState.Result<T>, rhs: AppState.Result<T>) -> Bool {\\n\\n            switch lhs {\\n            case .loading:\\n                if case loading = rhs {\\n                    return true\\n                }\\n            case .success(let lhsValue):\\n                if case success(let rhsValue) = rhs {\\n                    return rhsValue == lhsValue\\n                }\\n\\n            case .failure:\\n                if case failure = rhs {\\n                    return true\\n                }\\n            }\\n\\n            return false\\n        }\\n\\n        case loading\\n        case success(T)\\n        case failure\\n    }\\n\\n    struct  Api {\\n        var preferredPodcasts: [String: Result<[PodcastViewModel]>] = [:]\\n        var allGenres: Result<[GenreViewModel]> = .loading\\n        var podcast: Result<PodcastViewModel> = .loading\\n    }\\n\\n    struct Coredata {\\n        var savedGenres: [GenreViewModel] = []\\n        var savedPodcast: Result<PodcastViewModel> = .loading\\n        // highlight-start\\n        var bookmarkedPodcasts: Result<[PodcastViewModel]> = .loading\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23CoredataReducer1\\n// $$liquidcoderFilename-CoredataReducer\\n\\nimport Foundation\\n\\nstruct CoredataReducer {\\n\\n    static func execute(action: Action.Coredata, state: inout AppState.Coredata, environment: AppEnvironment) {\\n\\n        switch action {\\n\\n        case .fetchGenres:\\n            state.savedGenres = environment.coredata.fetchGenres()\\n        case .saveGenres(let genres):\\n            _ = environment.coredata.saveGenres(genres)\\n        case .fetchPodcast(let podcast):\\n            let managedPodcast = environment.coredata.fetchPodcast(podcast.id)\\n            updateState(&state, savedPodcast: managedPodcast, existingPodcast: podcast)\\n        case .bookmark(let podcast):\\n            if let managedPodcast = environment.coredata.bookmarkPodcast(podcast) {\\n                updateState(&state, savedPodcast: managedPodcast, existingPodcast: podcast)\\n            }\\n        case .unbookmarked(let podcast):\\n            let managedPodcast = environment.coredata.deleteBookmarked(podcast.id)\\n            updateState(&state, savedPodcast: managedPodcast, existingPodcast: podcast)\\n        // highlight-start\\n        case .fetchBookmarkedPodcasts:\\n            let podcasts = environment.coredata.fetchAllPodcasts(bookmarkedOnly: true)\\n            state.bookmarkedPodcasts = .success(podcasts)\\n        // highlight-end\\n        }\\n    }\\n\\n    private static func updateState(_ state: inout AppState.Coredata, savedPodcast: PodcastManagedObject?, existingPodcast: PodcastViewModel) {\\n        if let managedPodcast = savedPodcast {\\n            let podcast = PodcastViewModel(managedPodcast, existingPodcast: existingPodcast)\\n            state.savedPodcast = .success(podcast)\\n        } else {\\n            state.savedPodcast = .failure\\n        }\\n    }\\n}\\n\")), mdx(\"div\", {\n    id: \"Lesson23BookmarkedScreen1\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1615636603/StreamCast/Lesson%2023/zvxqxamtwb8hntkj1fs0.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1615636603/StreamCast/Lesson%2023/zvxqxamtwb8hntkj1fs0.png\"\n  }))))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23BookmarkedScreen2\\n// $$liquidcoderFilename-BookmarkedScreen\\n\\nimport SwiftUI\\n\\nstruct BookmarkedScreen: View {\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct BookmarkedScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        BookmarkedScreen()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23BookmarkedScreen3\\n// $$liquidcoderFilename-BookmarkedScreen\\n\\nimport SwiftUI\\n\\nstruct BookmarkedScreen: View {\\n\\n    // highlight-start\\n    @EnvironmentObject private var store: Store\\n    // highlight-end\\n\\n    // highlight-start\\n    private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct BookmarkedScreen_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n    // highlight-end\\n\\n    // highlight-start\\n    static var previews: some View {\\n        Group {\\n            BookmarkedScreen()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            BookmarkedScreen()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23BookmarkedScreen4\\n// $$liquidcoderFilename-BookmarkedScreen\\n\\nimport SwiftUI\\n\\nstruct BookmarkedScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n\\n    // highlight-start\\n    private func createContent(podcasts: [PodcastViewModel]) -> some View {\\n        ForEach(podcasts) { podcast in\\n            PodcastLinkView(podcast: podcast)\\n        }.padding(.horizontal)\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    private func createGrid(podcasts: [PodcastViewModel]) -> some View {\\n        LazyVGrid(columns: gridItems,\\n                  alignment: .leading,\\n                  spacing: 10,\\n                  content: { createContent(podcasts: podcasts) })\\n    }\\n    // highlight-end    \\n}\\n\\nstruct BookmarkedScreen_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            BookmarkedScreen()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            BookmarkedScreen()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23BookmarkedScreen5\\n// $$liquidcoderFilename-BookmarkedScreen\\n\\nimport SwiftUI\\n\\nstruct BookmarkedScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n\\n    var body: some View {\\n        // highlight-start\\n        ScrollView {\\n            switch store.coredataState.allPodcasts {\\n            case .loading:\\n                RedactedListView(viewType: .podcast)\\n                    .onAppear(perform: fetchPodcasts)\\n            case .success(let podcasts):\\n                createGrid(podcasts: podcasts)\\n            case .failure:\\n                FailureView(message: \\\"Unable to fetch saved podcasts \\\", retryAction: fetchPodcasts)\\n\\n            }\\n        }.navigationTitle(\\\"Bookmark\\\")\\n        // highlight-end\\n    }\\n\\n    // highlight-start\\n    private func fetchPodcasts() {\\n        store.dispatch(.coredata(.fetchBookmarkedPodcasts))\\n    }\\n    // highlight-end\\n\\n    private func createContent(podcasts: [PodcastViewModel]) -> some View {\\n        ForEach(podcasts) { podcast in\\n            PodcastLinkView(podcast: podcast)\\n        }.padding(.horizontal)\\n    }\\n\\n    private func createGrid(podcasts: [PodcastViewModel]) -> some View {\\n        LazyVGrid(columns: gridItems,\\n                  alignment: .leading,\\n                  spacing: 10,\\n                  content: { createContent(podcasts: podcasts) })\\n    }    \\n}\\n\\nstruct BookmarkedScreen_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            BookmarkedScreen()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            BookmarkedScreen()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n    }\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson23Intro-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey guys, welcome back to lesson #23. In the previous one, we started working on bookmarking podcasts, and in this one, we will  integrate that feature in views that will use it. \")), mdx(\"div\", {\n    id: \"Lesson23UpdatePodcastDetailContent1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating PodcastDetailContent\"), mdx(\"p\", null, \"We create the bookmark button that we add on the navigation bar using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"navigationBarItems\"), \" modifier.\")), mdx(\"div\", {\n    id: \"Lesson23UpdatePodcastDetailContent2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We then bring in the store that we use in the bookmark button's action. \"), mdx(\"p\", null, \"In the bookmark button's action closure, we dispatch an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"unbookmarked\"), \" action if the podcast is bookmarked, otherwise we we dispatch a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"bookmarked\"), \" one.\")), mdx(\"div\", {\n    id: \"Lesson23UpdatePodcastViewModel1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating PodcastViewModel\"), mdx(\"p\", null, \"We want to observe changes to the podcast view model, and update listening views. So to get updates when the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isBookmarked\"), \" property changes, we need add it in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Equatable\"), \"'s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"==\"), \" method.\")), mdx(\"div\", {\n    id: \"Lesson23CoredataServiceProtocol1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating CoredataServiceProtocol\"), mdx(\"p\", null, \"We are about to create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"BookmarkedScreen\"), \" that's why we need to create a method that will fetch all the saved podcasts from the database.\")), mdx(\"div\", {\n    id: \"Lesson23UpdatePreviewCoredataService1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating PreviewCoredataService\"), mdx(\"p\", null, \"As always, we need to update the implementations as well starting with the preview one.\")), mdx(\"div\", {\n    id: \"Lesson23UpdateCoredataService1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating CoredataService\"), mdx(\"p\", null, \"Then the real service.  Let's work on that next.\")), mdx(\"div\", {\n    id: \"Lesson23UpdateCoredataService2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Fetching multiple objects from coredata is the same as fetching a single, but here we are returning an array rather than a single object.\"), mdx(\"p\", null, \"We will add the predicate when the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"bookmarkedOnly\"), \" parameter is true to only fetch podcasts where \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isBookmarked\"), \" is set to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"true\"), \".\")), mdx(\"div\", {\n    id: \"Lesson23UpdateAction1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating Action\"), mdx(\"p\", null, \"Add the highlighted line in the Coredata enum\")), mdx(\"div\", {\n    id: \"Lesson23UpdateAppState1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating AppState\"), mdx(\"p\", null, \"Add the highlighted line in the Coredata struct\")), mdx(\"div\", {\n    id: \"Lesson23CoredataReducer1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating CoredataReducer\"), mdx(\"p\", null, \"By now, you already know the drill. We  need to update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CoredataReducer\"), \" having changed the Action.\"), mdx(\"hr\", null), mdx(\"p\", null, \"Here we are updating the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"allPodcasts\"), \" coredata state.\")), mdx(\"div\", {\n    id: \"Lesson23BookmarkedScreen1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"BookmarkedScreen\"), mdx(\"p\", null, \"The bookmarked screen will contain only bookmarked podcasts whether they have saved episodes or not.\")), mdx(\"div\", {\n    id: \"Lesson23BookmarkedScreen2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens\")), \" folder, create another folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Bookmarked\")), \", and inside it add a new swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"BookmarkedScreen\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\"))), mdx(\"div\", {\n    id: \"Lesson23BookmarkedScreen3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Let's bring the store to fetch podcasts with.\"), mdx(\"p\", null, \"Then we create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"gridItems\"), \" that  we still need to refactor.\"), mdx(\"hr\", null), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Finally, we set up the preview.\"))), mdx(\"div\", {\n    id: \"Lesson23BookmarkedScreen4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We first create the content that we will use in the grid created below.\")), mdx(\"div\", {\n    id: \"Lesson23BookmarkedScreen5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \", we switch on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"bookmarkedPodcasts\"), \" state property, and inside each case we return appropriate views.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"This entire view will be refactored in the next lesson.\"))), mdx(\"div\", {\n    id: \"Lesson23Outro-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"This is the end of lesson #23, see you in #24 where we will mainly refactor the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastListView\"), \" to be used with by the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"BookmarkedScreen\"), \" as well as the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DownloadedScreen\"), \".\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#updating-podcastdetailcontent","title":"Updating PodcastDetailContent"},{"url":"#updating-podcastviewmodel","title":"Updating PodcastViewModel"},{"url":"#updating-coredataserviceprotocol","title":"Updating CoredataServiceProtocol"},{"url":"#updating-previewcoredataservice","title":"Updating PreviewCoredataService"},{"url":"#updating-coredataservice","title":"Updating CoredataService"},{"url":"#updating-action","title":"Updating Action"},{"url":"#updating-appstate","title":"Updating AppState"},{"url":"#updating-coredatareducer","title":"Updating CoredataReducer"},{"url":"#bookmarkedscreen","title":"BookmarkedScreen"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey guys, welcome back to lesson #23. In the previous one, we started working on bookmarking podcasts, and in this one, we will  integrate that feature in views that will use it. \"), mdx(\"h2\", null, \"Updating PodcastDetailContent\"), mdx(\"p\", null, \"We create the bookmark button that we add on the navigation bar using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"navigationBarItems\"), \" modifier.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21UpdatePodcastDetailContent1\\n// $$liquidcoderFilename-PodcastDetailContent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailContent: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    ...\\n\\n    // highlight-start\\n    private var bookmarkButton: some View {\\n        return EmptyView()\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        GeometryReader { geometry in\\n            ScrollView(showsIndicators: false, content: {\\n                VStack {\\n                    createImage(size: geometry.size.width)\\n\\n                    VStack(alignment: .leading, spacing: 10) {\\n                        PodcastDetailHeaderComponent(podcast: podcast)\\n                        allEpisodeLink\\n                        Divider()\\n                        description\\n\\n                    }\\n                }\\n            }).navigationBarTitle(\\\"\\\", displayMode: .inline)\\n            // highlight-start\\n            .navigationBarItems(trailing: bookmarkButton)\\n            // highlight-end\\n        }\\n    }\\n\\n    private func createImage(size: CGFloat) -> some View {\\n        SCImageView(podcast: podcast, size: size, cornerRadius: 0)\\n            .aspectRatio(contentMode: .fill)\\n    }\\n}\\n\")), mdx(\"p\", null, \"We then bring in the store that we use in the bookmark button's action. \"), mdx(\"p\", null, \"In the bookmark button's action closure, we dispatch an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"unbookmarked\"), \" action if the podcast is bookmarked, otherwise we we dispatch a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"bookmarked\"), \" one.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21UpdatePodcastDetailContent2\\n// $$liquidcoderFilename-PodcastDetailContent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailContent: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    // highlight-start\\n    @EnvironmentObject private var store: Store\\n    // highlight-end\\n\\n    private var description: some View {\\n        Text(podcast.description)\\n            .padding(.horizontal)\\n            .fixedSize(horizontal: false, vertical: true)\\n    }\\n\\n    private var allEpisodeLink: some View {\\n        NavigationLink(destination: EpisodeListScreen(episodes: podcast.episodes)) {\\n            Label(\\\"See all \\\\(podcast.episodes.count) episodes\\\", systemImage: \\\"square.stack.3d.down.right.fill\\\")\\n        }.padding(.horizontal)\\n    }\\n\\n    private var bookmarkButton: some View {\\n        // highlight-start\\n        Button(action: {\\n            if podcast.isBookmarked {\\n                store.dispatch(.coredata(.unbookmarked(podcast)))\\n            } else {\\n                store.dispatch(.coredata(.bookmark(podcast)))\\n            }\\n        }, label: {\\n            Image(systemName:  podcast.isBookmarked ? \\\"heart.fill\\\" : \\\"heart\\\")\\n        })\\n        // highlight-end\\n    }\\n\\n    ...\\n\\n}\\n\")), mdx(\"h2\", null, \"Updating PodcastViewModel\"), mdx(\"p\", null, \"We want to observe changes to the podcast view model, and update listening views. So to get updates when the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isBookmarked\"), \" property changes, we need add it in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Equatable\"), \"'s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"==\"), \" method.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21UpdatePodcastViewModel1\\n// $$liquidcoderFilename-PodcastViewModel\\n\\nimport Foundation\\nimport FeedKit\\n\\nstruct PodcastViewModel: Identifiable, Equatable {\\n\\n    ...\\n\\n    static func == (lhs: PodcastViewModel, rhs: PodcastViewModel) -> Bool {\\n        return lhs.id == rhs.id\\n            && lhs.language == rhs.language\\n            && lhs.episodes.count == rhs.episodes.count\\n            && lhs.image.count == rhs.image.count\\n            && lhs.thumbnail.count == rhs.thumbnail.count\\n            // highlight-start\\n            && lhs.isBookmarked == rhs.isBookmarked\\n            // highlight-end\\n    }\\n}\\n\")), mdx(\"h2\", null, \"Updating CoredataServiceProtocol\"), mdx(\"p\", null, \"We are about to create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"BookmarkedScreen\"), \" that's why we need to create a method that will fetch all the saved podcasts from the database.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21CoredataServiceProtocol1\\n// $$liquidcoderFilename-CoredataServiceProtocol\\n\\nimport Foundation\\n\\nprotocol CoredataServiceProtocol {\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool\\n    func fetchGenres() -> [GenreViewModel]\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool\\n    func fetchEpisode(_ episode: EpisodeViewModel) -> EpisodeManagedObject?\\n    func fetchPodcast(_ id: Int) -> PodcastManagedObject?\\n    func bookmarkPodcast(_ podcast: PodcastViewModel) -> PodcastManagedObject?\\n    func unbookmarkedPodcast(_ id: Int) -> PodcastManagedObject?\\n    // highlight-start\\n    func fetchAllPodcasts(bookmarkedOnly: Bool) -> [PodcastViewModel]\\n    // highlight-end\\n}\\n\")), mdx(\"h2\", null, \"Updating PreviewCoredataService\"), mdx(\"p\", null, \"As always, we need to update the implementations as well starting with the preview one.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21UpdatePreviewCoredataService1\\n// $$liquidcoderFilename-PreviewCoredataService\\n\\nimport CoreData\\n\\nclass PreviewCoredataService: CoredataServiceProtocol {\\n\\n    // highlight-start\\n    func fetchAllPodcasts(bookmarkedOnly: Bool) -> [PodcastViewModel] {\\n        return PodcastViewModel.listPlaceholder\\n    }\\n    // highlight-end\\n\\n    func bookmarkPodcast(_ podcast: PodcastViewModel) -> PodcastManagedObject? {\\n        return nil\\n    }\\n\\n    func unbookmarkedPodcast(_ id: Int) -> PodcastManagedObject? {\\n        return nil\\n    }\\n\\n    func fetchEpisode(_ episode: EpisodeViewModel) -> EpisodeManagedObject? {\\n        return nil\\n    }\\n\\n    func fetchPodcast(_ id: Int) -> PodcastManagedObject? {\\n        return nil\\n    }\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool {\\n\\n        return true\\n    }\\n\\n    func fetchGenres() -> [GenreViewModel] {\\n        return GenreViewModel.listPlaceholders\\n    }\\n\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool {\\n        return false\\n    }\\n\\n}\\n\")), mdx(\"h2\", null, \"Updating CoredataService\"), mdx(\"p\", null, \"Then the real service.  Let's work on that next.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21UpdateCoredataService1\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n        ...\\n\\n    func fetchGenres() -> [GenreViewModel] {\\n        let request: NSFetchRequest<GenreManagedObject> = GenreManagedObject.fetchRequest()\\n\\n        do {\\n            let results = try context.fetch(request)\\n            return results.map { GenreViewModel(genre: $0) }\\n        } catch let error {\\n            Log.error(error)\\n            let nsError = error as NSError\\n            fatalError(\\\"Unresolved error \\\\(nsError), \\\\(nsError.userInfo)\\\")\\n        }\\n    }\\n\\n    // highlight-start\\n    func fetchAllPodcasts(bookmarkedOnly: Bool) -> [PodcastViewModel] {\\n\\n    }\\n    // highlight-end\\n\\n      ...\\n\\n}\\n\")), mdx(\"p\", null, \"Fetching multiple objects from coredata is the same as fetching a single, but here we are returning an array rather than a single object.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21UpdateCoredataService2\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n    ...\\n\\n    func fetchGenres() -> [GenreViewModel] {\\n        let request: NSFetchRequest<GenreManagedObject> = GenreManagedObject.fetchRequest()\\n\\n        do {\\n            let results = try context.fetch(request)\\n            return results.map { GenreViewModel(genre: $0) }\\n        } catch let error {\\n            Log.error(error)\\n            let nsError = error as NSError\\n            fatalError(\\\"Unresolved error \\\\(nsError), \\\\(nsError.userInfo)\\\")\\n        }\\n    }\\n\\n    func fetchAllPodcasts(bookmarkedOnly: Bool) -> [PodcastViewModel] {\\n        // highlight-start\\n        let request: NSFetchRequest<PodcastManagedObject> = PodcastManagedObject.fetchRequest()\\n\\n        if bookmarkedOnly {\\n            request.predicate = NSPredicate(format: \\\"isBookmarked = %@\\\", \\\"1\\\")\\n        }\\n\\n        do {\\n            let results = try context.fetch(request)\\n            return results.map { PodcastViewModel($0) }\\n        } catch let error {\\n            Log.error(error)\\n        }\\n\\n                return []\\n        // highlight-end\\n    }\\n\\n        ... \\n\\n}\\n\")), mdx(\"p\", null, \"We will add the predicate when the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"bookmarkedOnly\"), \" parameter is true to only fetch podcasts where \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isBookmarked\"), \" is set to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"true\"), \".\"), mdx(\"h2\", null, \"Updating Action\"), mdx(\"p\", null, \"Add the highlighted line in the Coredata enum\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21UpdateAction1\\n// $$liquidcoderFilename-Action\\n\\nimport Foundation\\n\\nenum Action {\\n    enum Api {\\n        case fetchPodcasts(_ term: String, limit: Int = 10)\\n        case fetchGenres(_ filename: String)\\n        case updatePodcast(_ podcast: PodcastViewModel)\\n    }\\n\\n    enum Coredata {\\n        case fetchGenres\\n        case saveGenres(_ genres: [GenreViewModel])\\n        case fetchPodcast(_ podcast: PodcastViewModel)\\n        case bookmark(_ podcast: PodcastViewModel)\\n        case unbookmarked(_ podcast: PodcastViewModel)\\n        // highlight-start\\n        case fetchBookmarkedPodcasts\\n        // highlight-end\\n    }\\n\\n    case api(_ action: Api)\\n    case coredata(_ action: Coredata)\\n\\n}\\n\")), mdx(\"h2\", null, \"Updating AppState\"), mdx(\"p\", null, \"Add the highlighted line in the Coredata struct\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21UpdateAppState1\\n// $$liquidcoderFilename-AppState\\n\\nimport Foundation\\nimport SwiftUI\\n\\nenum AppState {\\n\\n    enum Result<T: Equatable>: Equatable {\\n        static func == (lhs: AppState.Result<T>, rhs: AppState.Result<T>) -> Bool {\\n\\n            switch lhs {\\n            case .loading:\\n                if case loading = rhs {\\n                    return true\\n                }\\n            case .success(let lhsValue):\\n                if case success(let rhsValue) = rhs {\\n                    return rhsValue == lhsValue\\n                }\\n\\n            case .failure:\\n                if case failure = rhs {\\n                    return true\\n                }\\n            }\\n\\n            return false\\n        }\\n\\n        case loading\\n        case success(T)\\n        case failure\\n    }\\n\\n    struct  Api {\\n        var preferredPodcasts: [String: Result<[PodcastViewModel]>] = [:]\\n        var allGenres: Result<[GenreViewModel]> = .loading\\n        var podcast: Result<PodcastViewModel> = .loading\\n    }\\n\\n    struct Coredata {\\n        var savedGenres: [GenreViewModel] = []\\n        var savedPodcast: Result<PodcastViewModel> = .loading\\n        // highlight-start\\n                var bookmarkedPodcasts: Result<[PodcastViewModel]> = .loading\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"h2\", null, \"Updating CoredataReducer\"), mdx(\"p\", null, \"By now, you already know the drill. We need to update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CoredataReducer\"), \" having changed the Action.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21CoredataReducer1\\n// $$liquidcoderFilename-CoredataReducer\\n\\nimport Foundation\\n\\nstruct CoredataReducer {\\n\\n    static func execute(action: Action.Coredata, state: inout AppState.Coredata, environment: AppEnvironment) {\\n\\n        switch action {\\n\\n        case .fetchGenres:\\n            state.savedGenres = environment.coredata.fetchGenres()\\n        case .saveGenres(let genres):\\n            _ = environment.coredata.saveGenres(genres)\\n        case .fetchPodcast(let podcast):\\n            let managedPodcast = environment.coredata.fetchPodcast(podcast.id)\\n            updateState(&state, savedPodcast: managedPodcast, existingPodcast: podcast)\\n        case .bookmark(let podcast):\\n            if let managedPodcast = environment.coredata.bookmarkPodcast(podcast) {\\n                updateState(&state, savedPodcast: managedPodcast, existingPodcast: podcast)\\n            }\\n        case .unbookmarked(let podcast):\\n            let managedPodcast = environment.coredata.deleteBookmarked(podcast.id)\\n            updateState(&state, savedPodcast: managedPodcast, existingPodcast: podcast)\\n        // highlight-start\\n        case .fetchBookmarkedPodcasts:\\n            let podcasts = environment.coredata.fetchAllPodcasts(bookmarkedOnly: true)\\n            state.bookmarkedPodcasts = .success(podcasts)\\n        // highlight-end\\n        }\\n    }\\n\\n    private static func updateState(_ state: inout AppState.Coredata, savedPodcast: PodcastManagedObject?, existingPodcast: PodcastViewModel) {\\n        if let managedPodcast = savedPodcast {\\n            let podcast = PodcastViewModel(managedPodcast, existingPodcast: existingPodcast)\\n            state.savedPodcast = .success(podcast)\\n        } else {\\n            state.savedPodcast = .failure\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"Here we are updating the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"allPodcasts\"), \" coredata state.\"), mdx(\"h2\", null, \"BookmarkedScreen\"), mdx(\"p\", null, \"The bookmarked screen will contain only bookmarked podcasts whether they have saved episodes or not.\"), mdx(\"p\", null, \"Inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens\")), \" folder, create another folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Bookmarked\")), \", and inside it add a new swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"BookmarkedScreen\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21BookmarkedScreen2\\n// $$liquidcoderFilename-BookmarkedScreen\\n\\nimport SwiftUI\\n\\nstruct BookmarkedScreen: View {\\n\\n    // highlight-start\\n    @EnvironmentObject private var store: Store\\n    // highlight-end\\n\\n    // highlight-start\\n    private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct BookmarkedScreen_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n    // highlight-end\\n\\n    // highlight-start\\n    static var previews: some View {\\n        Group {\\n            BookmarkedScreen()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            BookmarkedScreen()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"p\", null, \"Let's bring the store to fetch podcasts with.\"), mdx(\"p\", null, \"Then we create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"gridItems\"), \" that  we still need to refactor.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Finally, we set up the preview.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21BookmarkedScreen1\\n// $$liquidcoderFilename-BookmarkedScreen\\n\\nimport SwiftUI\\n\\nstruct BookmarkedScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n\\n    // highlight-start\\n    private func createContent(podcasts: [PodcastViewModel]) -> some View {\\n        ForEach(podcasts) { podcast in\\n            PodcastLinkView(podcast: podcast)\\n        }.padding(.horizontal)\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    private func createGrid(podcasts: [PodcastViewModel]) -> some View {\\n        LazyVGrid(columns: gridItems,\\n                  alignment: .leading,\\n                  spacing: 10,\\n                  content: { createContent(podcasts: podcasts) })\\n    }\\n    // highlight-end    \\n}\\n\\nstruct BookmarkedScreen_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            BookmarkedScreen()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            BookmarkedScreen()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"p\", null, \"We first create the content that we will use in the grid created below.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson21BookmarkedScreen1\\n// $$liquidcoderFilename-BookmarkedScreen\\n\\nimport SwiftUI\\n\\nstruct BookmarkedScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n\\n    var body: some View {\\n        // highlight-start\\n        ScrollView {\\n            switch store.coredataState.bookmarkedPodcasts {\\n            case .loading:\\n                RedactedListView(viewType: .podcast)\\n                    .onAppear(perform: fetchPodcasts)\\n            case .success(let podcasts):\\n                createGrid(podcasts: podcasts)\\n            case .failure:\\n                FailureView(message: \\\"Unable to fetch saved podcasts \\\", retryAction: fetchPodcasts)\\n\\n            }\\n        }.navigationTitle(\\\"Bookmark\\\")\\n        // highlight-end\\n    }\\n\\n    // highlight-start\\n    private func fetchPodcasts() {\\n        store.dispatch(.coredata(.fetchBookmarkedPodcasts))\\n    }\\n    // highlight-end\\n\\n    private func createContent(podcasts: [PodcastViewModel]) -> some View {\\n        ForEach(podcasts) { podcast in\\n            PodcastLinkView(podcast: podcast)\\n        }.padding(.horizontal)\\n    }\\n\\n    private func createGrid(podcasts: [PodcastViewModel]) -> some View {\\n        LazyVGrid(columns: gridItems,\\n                  alignment: .leading,\\n                  spacing: 10,\\n                  content: { createContent(podcasts: podcasts) })\\n    }    \\n}\\n\\nstruct BookmarkedScreen_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            BookmarkedScreen()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            BookmarkedScreen()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"p\", null, \"In \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \", we switch on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"bookmarkedPodcasts\"), \" state property, and inside each case we return appropriate views.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"This entire view will be refactored in the next lesson.\")), mdx(\"p\", null, \"This is the end of lesson #23, see you in #24 where we will mainly refactor the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastListView\"), \" to be used with by the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"BookmarkedScreen\"), \" as well as the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DownloadedScreen\"), \".\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#updating-podcastdetailcontent","title":"Updating PodcastDetailContent"},{"url":"#updating-podcastviewmodel","title":"Updating PodcastViewModel"},{"url":"#updating-coredataserviceprotocol","title":"Updating CoredataServiceProtocol"},{"url":"#updating-previewcoredataservice","title":"Updating PreviewCoredataService"},{"url":"#updating-coredataservice","title":"Updating CoredataService"},{"url":"#updating-action","title":"Updating Action"},{"url":"#updating-appstate","title":"Updating AppState"},{"url":"#updating-coredatareducer","title":"Updating CoredataReducer"},{"url":"#bookmarkedscreen","title":"BookmarkedScreen"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-03-24T09:06:30.415Z","isNewLayout":true,"previews":null,"slug":"lesson-24-refactoring-and-downloadscreen","title":"Lesson #24: Refactoring & DownloadScreen","updatedDate":"2021-03-24T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZ7C22XZQFvQ76xaLiXPyTUJ75HEQhb6nsRk","type":"zip","id":"122d633c-da89-5c1f-8043-fde8730075ff","name":"Source code"}],"allChapters":null,"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson24UpdatePodcastListContent1\\n// $$liquidcoderFilename-PodcastListContent\\n\\nimport SwiftUI\\n\\nstruct PodcastListContent: View {\\n\\n    // highlight-start\\n    var state: AppState.Result<[PodcastViewModel]> = .loading\\n    // highlight-end\\n\\n    // highlight-start\\n    var fetchPodcasts: (() -> Void)\\n    // highlight-end\\n\\n    var body: some View {\\n        switch state {\\n        case .loading:\\n            RedactedListView(viewType: .podcast)\\n                .onAppear(perform: fetchPodcasts)\\n        case .success(let podcasts):\\n            successContent(podcasts: podcasts)\\n        case .failure:\\n            // highlight-start\\n            FailureView(message: \\\"Unable to fetch podcasts\\\", retryAction: fetchPodcasts)\\n            // highlight-end\\n        }\\n    }\\n\\n    private func successContent(podcasts: [PodcastViewModel]) -> some View {\\n        ForEach(podcasts) { podcast in\\n            PodcastLinkView(podcast: podcast)\\n        }.padding(.horizontal)\\n    }\\n}\\n\\nstruct PodcastListContent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            PodcastListContent(state: .loading, fetchPodcasts: {})\\n                .previewLayout(.sizeThatFits)\\n                .colorScheme(.dark)\\n            PodcastListContent(state: .loading, fetchPodcasts: {})\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson24UpdateHomeMyLibrarySection\\n// $$liquidcoderFilename-HomeMyLibrarySection\\n\\nimport SwiftUI\\n\\nstruct HomeMyLibrarySection: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    @State private var selectedGenre = \\\"\\\"\\n\\n    private var section: some View {\\n        Section(header: HomeMyLibrarySectionHeader(selected: $selectedGenre)) {\\n            // highlight-start\\n            PodcastListContent(state: getState(for: selectedGenre), fetchPodcasts: fetchPodcasts)\\n            // highlight-end\\n        }\\n    }\\n\\n    var body: some View {\\n        LazyVGrid(columns: GridItems.shared,\\n                  alignment: .leading,\\n                  spacing: 10,\\n                  pinnedViews: [.sectionHeaders],\\n                  content: { section })\\n    }\\n\\n    // highlight-start\\n    private func fetchPodcasts() {\\n        store.dispatch(.api(.fetchPodcasts(selectedGenre, limit: 10)))\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    private func getState(for term: String) -> AppState.Result<[PodcastViewModel]> {\\n        if let state = store.apiState.preferredPodcasts[term] {\\n            return state\\n        }\\n        return .loading\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson24UpdatePodcastListScreen1\\n// $$liquidcoderFilename-PodcastListScreen\\n\\nimport SwiftUI\\n\\nstruct PodcastListScreen: View {\\n\\n    var genre: String\\n\\n    // highlight-start\\n    @EnvironmentObject private var store: Store\\n    // highlight-end\\n\\n    private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n\\n    private var grid: some View {\\n\\n        LazyVGrid(columns: gridItems,\\n                  alignment: .leading,\\n                  spacing: 10,\\n                  // highlight-start\\n                  content: { PodcastListContent(state: getState(for: genre) , fetchPodcasts: fetchPodcasts) })\\n                // highlight-end\\n    }\\n\\n    var body: some View {\\n        ScrollView {\\n            grid\\n        }.navigationTitle(genre)\\n    }\\n\\n    // highlight-start\\n    private func fetchPodcasts() {\\n        store.dispatch(.api(.fetchPodcasts(genre, limit: 10)))\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    private func getState(for term: String) -> AppState.Result<[PodcastViewModel]> {\\n        if let state = store.apiState.preferredPodcasts[term] {\\n            return state\\n        }\\n        return .loading\\n    }\\n    // highlight-end\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson24UpdateBookmarkedScreen1\\n// $$liquidcoderFilename-BookmarkedScreen\\n\\nimport SwiftUI\\n\\nstruct BookmarkedScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n\\n    // highlight-start\\n    private var grid: some View {\\n        LazyVGrid(columns: gridItems,\\n                  alignment: .leading,\\n                  spacing: 10,\\n                  content: { PodcastListContent(state: store.coredataState.bookmarkedPodcasts, fetchPodcasts: fetchPodcasts) })\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        ScrollView {\\n            // highlight-start\\n            grid\\n            // highlight-end\\n        }.navigationTitle(\\\"Bookmark\\\")\\n    }\\n\\n    private func fetchPodcasts() {\\n        store.dispatch(.coredata(.fetchAllManagedPodcast(true)))\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson24UpdateFailureView1\\n// $$liquidcoderFilename-FailureView\\n\\nimport SwiftUI\\n\\nstruct FailureView: View {\\n\\n    ...\\n\\n    var body: some View {\\n\\n        ZStack {\\n\\n            VStack {\\n\\n                createBackgroundOverlay(mask: \\\"EmptyScreenOverlay1\\\")\\n                Spacer()\\n                createBackgroundOverlay(mask: \\\"EmptyScreenOverlay2\\\")\\n\\n             // highlight-start\\n            }.frame(height: showIcon ? UIScreen.main.bounds.height : 200)\\n            // highlight-end\\n            .edgesIgnoringSafeArea(.all)\\n\\n            VStack(spacing: 40) {\\n                imageView\\n                messageView\\n                retryButton\\n            }\\n        }\\n    }\\n\\n    fileprivate func createBackgroundOverlay(mask: String) -> some View {\\n        return Rectangle()\\n            .foregroundColor(Color.overlay)\\n            .frame(height: 300)\\n            .mask(\\n                Image(mask)\\n                    .resizable()\\n                    .frame(height: 300))\\n            // highlight-start\\n            .offset(x: 0, y: 50)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson24UpdateCoredataServiceProtocol1\\n// $$liquidcoderFilename-CoredataServiceProtocol\\n\\nimport Foundation\\n\\nprotocol CoredataServiceProtocol {\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool\\n    func fetchGenres() -> [GenreViewModel]\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool\\n    func fetchEpisode(_ episode: EpisodeViewModel) -> EpisodeManagedObject?\\n    func fetchPodcast(_ id: Int) -> PodcastManagedObject?\\n    func bookmarkPodcast(_ podcast: PodcastViewModel) -> PodcastManagedObject?\\n    func deleteBookmarked(_ id: Int) -> PodcastManagedObject?\\n    // highlight-start\\n    func fetchAllPodcasts(bookmarkedOnly: Bool, onlyContainingEpisodes flag: Bool) -> [PodcastViewModel]\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson24UpdatePreviewCoredataService1\\n// $$liquidcoderFilename-PreviewCoredataService\\n\\nimport CoreData\\n\\nclass PreviewCoredataService: CoredataServiceProtocol {\\n\\n    // highlight-start\\n    func fetchAllPodcasts(bookmarkedOnly: Bool, onlyContainingEpisodes flag: Bool) -> [PodcastViewModel] {\\n        return PodcastViewModel.listPlaceholder\\n    }\\n    // highlight-end\\n\\n       ...\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson24UpdateCoredataService1\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n    ...\\n\\n    func fetchAllPodcasts(bookmarkedOnly: Bool, onlyContainingEpisodes flag: Bool) -> [PodcastViewModel] {\\n\\n        let request: NSFetchRequest<PodcastManagedObject> = PodcastManagedObject.fetchRequest()\\n\\n        if bookmarkedOnly {\\n            request.predicate = NSPredicate(format: \\\"isBookmarked = %@\\\", \\\"1\\\")\\n        }\\n\\n        do {\\n            let results = try context.fetch(request)\\n\\n            // highlight-start\\n            if flag {\\n                return results\\n                    .filter { !$0.episodesArray.isEmpty }\\n                    .map { PodcastViewModel($0) }\\n            }\\n            // highlight-end\\n\\n            return results.map {\\n                PodcastViewModel($0)\\n            }           \\n        } catch let error {\\n            Log.error(error)\\n        }\\n\\n        return []\\n    }\\n\\n    ...\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson24UpdateAction1\\n// $$liquidcoderFilename-Action\\n\\nimport Foundation\\n\\nenum Action {\\n    enum Api {\\n        case fetchPodcasts(_ term: String, limit: Int = 10)\\n        case fetchGenres(_ filename: String)\\n        case updatePodcast(_ podcast: PodcastViewModel)\\n    }\\n\\n    enum Coredata {\\n        case fetchGenres\\n        case saveGenres(_ genres: [GenreViewModel])\\n        case fetchPodcast(_ podcast: PodcastViewModel)\\n        case bookmark(_ podcast: PodcastViewModel)\\n        case deleteBookmarked(_ podcast: PodcastViewModel)\\n        case fetchBookmarkedPodcasts\\n        // highlight-start\\n        case fetchPodcastsWithEpisodes\\n        // highlight-end\\n    }\\n\\n    case api(_ action: Api)\\n    case coredata(_ action: Coredata)\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson24UpdateAppState1\\n// $$liquidcoderFilename-AppState\\n\\nimport Foundation\\nimport SwiftUI\\n\\nenum AppState {\\n\\n    ...\\n\\n    struct  Api {\\n        var preferredPodcasts: [String: Result<[PodcastViewModel]>] = [:]\\n        var allGenres: Result<[GenreViewModel]> = .loading\\n        var podcast: Result<PodcastViewModel> = .loading\\n    }\\n\\n    struct Coredata {\\n        var savedGenres: [GenreViewModel] = []\\n        var savedPodcast: Result<PodcastViewModel> = .loading\\n        var bookmarkedPodcasts: Result<[PodcastViewModel]> = .loading\\n        // highlight-start\\n        var podcastsWithEpisodes: Result<[PodcastViewModel]> = .loading\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson24CoredataReducer1\\n// $$liquidcoderFilename-CoredataReducer\\n\\nimport Foundation\\n\\nstruct CoredataReducer {\\n\\n    static func execute(action: Action.Coredata, state: inout AppState.Coredata, environment: AppEnvironment) {\\n\\n        switch action {\\n\\n        case .fetchGenres:\\n            state.savedGenres = environment.coredata.fetchGenres()\\n        case .saveGenres(let genres):\\n            _ = environment.coredata.saveGenres(genres)\\n        case .fetchPodcast(let podcast):\\n            let managedPodcast = environment.coredata.fetchPodcast(podcast.id)\\n            updateState(&state, savedPodcast: managedPodcast, existingPodcast: podcast)\\n        case .bookmark(let podcast):\\n            if let managedPodcast = environment.coredata.bookmarkPodcast(podcast) {\\n                updateState(&state, savedPodcast: managedPodcast, existingPodcast: podcast)\\n            }\\n        case .deleteBookmarked(let podcast):\\n            let managedPodcast = environment.coredata.deleteBookmarked(podcast.id)\\n            updateState(&state, savedPodcast: managedPodcast, existingPodcast: podcast)\\n        case .fetchBookmarkedPodcasts:\\n            // highlight-start\\n            let podcasts = environment.coredata.fetchAllPodcasts(bookmarkedOnly: true, onlyContainingEpisodes: false)\\n            // highlight-end\\n            state.bookmarkedPodcasts = .success(podcasts)\\n\\n        // highlight-start\\n        case .fetchPodcastsWithEpisodes:\\n            let podcasts = environment.coredata.fetchAllPodcasts(bookmarkedOnly: false, onlyContainingEpisodes: true)\\n            state.podcastsWithEpisodes = .success(podcasts)\\n        // highlight-end\\n        }\\n    }\\n\\n    ...\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson24DownloadedScreen1\\n// $$liquidcoderFilename-DownloadedScreen\\n\\nimport SwiftUI\\n\\nstruct DownloadedScreen: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct DownloadedScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        DownloadedScreen()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson24DownloadedScreen2\\n// $$liquidcoderFilename-DownloadedScreen\\n\\nimport SwiftUI\\n\\nstruct DownloadedScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n\\n    // highlight-start\\n    private var grid: some View {\\n        LazyVGrid(columns: gridItems,\\n                  alignment: .leading,\\n                  spacing: 10,\\n                  content: { PodcastListContent(state: store.coredataState.allEpisodes, fetchPodcasts: fetchPodcasts) })\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        ScrollView {\\n            // highlight-start\\n            grid\\n            // highlight-end\\n        }.navigationTitle(\\\"Downloaded\\\")\\n\\n    }\\n\\n    private func fetchPodcasts() {\\n        store.dispatch(.coredata(.fetchPodcastsWithEpisodes))\\n    }\\n}\\n\\nstruct DownloadedScreen_Previews: PreviewProvider {\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            DownloadedScreen()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            DownloadedScreen()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n    }\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson24Intro-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey ladies and gents, welcome back to lesson #24. In the previous, we finished implementing the bookmarking feature, and in this one we will focus on refactoring the screens that we worked on in previous lesson.\")), mdx(\"div\", {\n    id: \"Lesson24UpdatePodcastListContent1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Refactoring PodcastListContent\"), mdx(\"p\", null, \"So let's start by declaring 2 properties that will be exposed to the calling site.\"), mdx(\"hr\", null), mdx(\"p\", null, \"We replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fetchPodcasts\"), \" with a closure because we want the fetch action to be handled by the callers of this view.\"), mdx(\"p\", null, \"We also update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"FailureView\"), \" to be more generic.\")), mdx(\"div\", {\n    id: \"Lesson24UpdateHomeMyLibrarySection-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Refactoring HomeMyLibrarySection\"), mdx(\"p\", null, \"Now, replace the call to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastListContent\"), \" with the highlighted line.\"), mdx(\"hr\", null), mdx(\"p\", null, \"We also create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fetchPodcasts\"), \" and the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"getState\"), \" method here, and removed them from the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastListContent\"))), mdx(\"div\", {\n    id: \"Lesson24UpdatePodcastListScreen1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Refactoring PodcastListScreen\"), mdx(\"p\", null, \"Here we are doing the same thing as what we've just done in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeMyLibrarySection\"), \" view.\")), mdx(\"div\", {\n    id: \"Lesson24UpdateBookmarkedScreen1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Refactoring BookmarkedScreen\"), mdx(\"p\", null, \"Same thing here as well, except we don't have the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"getState\"), \" method.\")), mdx(\"div\", {\n    id: \"Lesson24UpdateFailureView1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating FailureView\"), mdx(\"p\", null, \"We need to make a small change in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"FailureView\"), \" as well. So, update the content with the highlighted lines.\")), mdx(\"div\", {\n    id: \"Lesson24UpdateCoredataServiceProtocol1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating CoredataServiceProtocol\"), mdx(\"p\", null, \"Let's update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fetchAllPodcasts\"), \" to also filter only podcasts containing saved episodes, so add the second parameter.\")), mdx(\"div\", {\n    id: \"Lesson24UpdatePreviewCoredataService1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating PreviewCoredataService\"), mdx(\"p\", null, \"Let's again update the preview coredata service as well.\")), mdx(\"div\", {\n    id: \"Lesson24UpdateCoredataService1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating CoredataService\"), mdx(\"p\", null, \"Here, we filter out only podcasts where episodes array is not empty, and then create viewmodels from them.\")), mdx(\"div\", {\n    id: \"Lesson24UpdateAction1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating Action\"), mdx(\"p\", null, \"Let's add an action to fetch podcast containing episodes.\")), mdx(\"div\", {\n    id: \"Lesson24UpdateAppState1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating AppState\"), mdx(\"p\", null, \"We also need to track the state of podcast containing episodes, so we create a property to do that. \")), mdx(\"div\", {\n    id: \"Lesson24CoredataReducer1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating CoredataReducer\"), mdx(\"p\", null, \"We first need to update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fetchBookmarkedPodcasts\"), \" block's \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fetchAllPodcasts\"), \" method, then we add \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fetchPodcastsWithEpisodes\"), \" which does the same thing as the above case, but update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"podcastsWithEpisodes\"), \".\")), mdx(\"div\", {\n    id: \"Lesson24DownloadedScreen1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"DownloadedScreen\"), mdx(\"p\", null, \"Inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens\")), \" folder, create another folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Downloaded\")), \", and inside it add a new swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"DownloadedScreen\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\"))), mdx(\"div\", {\n    id: \"Lesson24DownloadedScreen2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The code in this screen is almost identical with the one written in then \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"BookmarkedScreen\"), \", the only difference here is the action sent.\")), mdx(\"div\", {\n    id: \"Lesson24Outro-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And this bring this refactoring lesson #24 to close, let's work on next one where we will implement the search feature. Happy coding!!\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#refactoring-podcastlistcontent","title":"Refactoring PodcastListContent"},{"url":"#refactoring-homemylibrarysection","title":"Refactoring HomeMyLibrarySection"},{"url":"#refactoring-podcastlistscreen","title":"Refactoring PodcastListScreen"},{"url":"#refactoring-bookmarkedscreen","title":"Refactoring BookmarkedScreen"},{"url":"#updating-failureview","title":"Updating FailureView"},{"url":"#updating-coredataserviceprotocol","title":"Updating CoredataServiceProtocol"},{"url":"#updating-previewcoredataservice","title":"Updating PreviewCoredataService"},{"url":"#updating-coredataservice","title":"Updating CoredataService"},{"url":"#updating-action","title":"Updating Action"},{"url":"#updating-appstate","title":"Updating AppState"},{"url":"#updating-coredatareducer","title":"Updating CoredataReducer"},{"url":"#downloadedscreen","title":"DownloadedScreen"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey ladies and gents, welcome back to lesson #24. In the previous, we finished implementing the bookmarking feature, and in this one we will focus on refactoring the screens that we worked on in previous lesson.\"), mdx(\"h2\", null, \"Refactoring PodcastListContent\"), mdx(\"p\", null, \"So let's start by declaring 2 properties that will be exposed to the calling site.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23UpdatePodcastListContent1\\n// $$liquidcoderFilename-PodcastListContent\\n\\nimport SwiftUI\\n\\nstruct PodcastListContent: View {\\n\\n    // highlight-start\\n    var state: AppState.Result<[PodcastViewModel]> = .loading\\n    // highlight-end\\n\\n    // highlight-start\\n    var fetchPodcasts: (() -> Void)\\n    // highlight-end\\n\\n    var body: some View {\\n        switch state {\\n        case .loading:\\n            RedactedListView(viewType: .podcast)\\n                .onAppear(perform: fetchPodcasts)\\n        case .success(let podcasts):\\n            successContent(podcasts: podcasts)\\n        case .failure:\\n            // highlight-start\\n            FailureView(message: \\\"Unable to fetch podcasts\\\", retryAction: fetchPodcasts)\\n            // highlight-end\\n        }\\n    }\\n\\n    private func successContent(podcasts: [PodcastViewModel]) -> some View {\\n        ForEach(podcasts) { podcast in\\n            PodcastLinkView(podcast: podcast)\\n        }.padding(.horizontal)\\n    }\\n}\\n\\nstruct PodcastListContent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            PodcastListContent(state: .loading, fetchPodcasts: {})\\n                .previewLayout(.sizeThatFits)\\n                .colorScheme(.dark)\\n            PodcastListContent(state: .loading, fetchPodcasts: {})\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"We replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fetchPodcasts\"), \" with a closure because we want the fetch action to be handled by the callers of this view.\"), mdx(\"p\", null, \"We also update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"FailureView\"), \" to be more generic.\"), mdx(\"h2\", null, \"Refactoring HomeMyLibrarySection\"), mdx(\"p\", null, \"Now, replace the call to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastListContent\"), \" with the highlighted line.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23UpdateHomeMyLibrarySection\\n// $$liquidcoderFilename-HomeMyLibrarySection\\n\\nimport SwiftUI\\n\\nstruct HomeMyLibrarySection: View {\\n\\n            // highlight-start\\n    @EnvironmentObject private var store: Store\\n            // highlight-end    \\n\\n    @State private var selectedGenre = \\\"\\\"\\n\\n    private var section: some View {\\n        Section(header: HomeMyLibrarySectionHeader(selected: $selectedGenre)) {\\n            // highlight-start\\n            PodcastListContent(state: getState(for: selectedGenre), fetchPodcasts: fetchPodcasts)\\n            // highlight-end\\n        }\\n    }\\n\\n    var body: some View {\\n        LazyVGrid(columns: GridItems.shared,\\n                  alignment: .leading,\\n                  spacing: 10,\\n                  pinnedViews: [.sectionHeaders],\\n                  content: { section })\\n    }\\n\\n    // highlight-start\\n    private func fetchPodcasts() {\\n        store.dispatch(.api(.fetchPodcasts(selectedGenre, limit: 10)))\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    private func getState(for term: String) -> AppState.Result<[PodcastViewModel]> {\\n        if let state = store.apiState.preferredPodcasts[term] {\\n            return state\\n        }\\n        return .loading\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"p\", null, \"We also create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fetchPodcasts\"), \" and the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"getState\"), \" method here, and removed them from the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastListContent\")), mdx(\"h2\", null, \"Refactoring PodcastListScreen\"), mdx(\"p\", null, \"Here we are doing the same thing as what we've just done in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeMyLibrarySection\"), \" view.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23UpdatePodcastListScreen1\\n// $$liquidcoderFilename-PodcastListScreen\\n\\nimport SwiftUI\\n\\nstruct PodcastListScreen: View {\\n\\n    var genre: String\\n\\n    // highlight-start\\n    @EnvironmentObject private var store: Store\\n    // highlight-end\\n\\n        private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n\\n    private var grid: some View {\\n\\n        LazyVGrid(columns: gridItems,\\n                  alignment: .leading,\\n                  spacing: 10,\\n                  // highlight-start\\n                  content: { PodcastListContent(state: getState(for: genre) , fetchPodcasts: fetchPodcasts) })\\n                // highlight-end\\n    }\\n\\n    var body: some View {\\n        ScrollView {\\n            grid\\n        }.navigationTitle(term)\\n    }\\n\\n    // highlight-start\\n    private func fetchPodcasts() {\\n        store.dispatch(.api(.fetchPodcasts(genre, limit: 10)))\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    private func getState(for term: String) -> AppState.Result<[PodcastViewModel]> {\\n        if let state = store.apiState.preferredPodcasts[term] {\\n            return state\\n        }\\n        return .loading\\n    }\\n    // highlight-end\\n\\n}\\n\")), mdx(\"h2\", null, \"Refactoring BookmarkedScreen\"), mdx(\"p\", null, \"Same thing here as well, except we don't have the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"getState\"), \" method.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23UpdateBookmarkedScreen1\\n// $$liquidcoderFilename-BookmarkedScreen\\n\\nimport SwiftUI\\n\\nstruct BookmarkedScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n\\n    // highlight-start\\n    private var grid: some View {\\n        LazyVGrid(columns: gridItems,\\n                  alignment: .leading,\\n                  spacing: 10,\\n                  content: { PodcastListContent(state: store.coredataState.bookmarkedPodcasts, fetchPodcasts: fetchPodcasts) })\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        ScrollView {\\n            // highlight-start\\n            grid\\n            // highlight-end\\n        }.navigationTitle(\\\"Bookmark\\\")\\n    }\\n\\n    private func fetchPodcasts() {\\n        store.dispatch(.coredata(.fetchAllManagedPodcast(true)))\\n    }\\n}\\n\")), mdx(\"h2\", null, \"Updating FailureView\"), mdx(\"p\", null, \"We need to make a small change in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"FailureView\"), \" as well. So, update the content with the highlighted lines.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23UpdateFailureView1\\n// $$liquidcoderFilename-FailureView\\n\\nimport SwiftUI\\n\\nstruct FailureView: View {\\n\\n    ...\\n\\n    var body: some View {\\n\\n        ZStack {\\n\\n            VStack {\\n\\n                createBackgroundOverlay(mask: \\\"EmptyScreenOverlay1\\\")\\n                Spacer()\\n                createBackgroundOverlay(mask: \\\"EmptyScreenOverlay2\\\")\\n\\n             // highlight-start\\n            }.frame(height: showIcon ? UIScreen.main.bounds.height : 200)\\n            // highlight-end\\n            .edgesIgnoringSafeArea(.all)\\n\\n            VStack(spacing: 40) {\\n                imageView\\n                messageView\\n                retryButton\\n            }\\n        }\\n    }\\n\\n    fileprivate func createBackgroundOverlay(mask: String) -> some View {\\n        return Rectangle()\\n            .foregroundColor(Color.overlay)\\n            .frame(height: 300)\\n            .mask(\\n                Image(mask)\\n                    .resizable()\\n                    .frame(height: 300))\\n            // highlight-start\\n            .offset(x: 0, y: 50)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"h2\", null, \"Updating CoredataServiceProtocol\"), mdx(\"p\", null, \"Let's update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fetchAllPodcasts\"), \" to also filter only podcasts containing saved episodes, so add the second parameter.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23UpdateCoredataServiceProtocol1\\n// $$liquidcoderFilename-CoredataServiceProtocol\\n\\nimport Foundation\\n\\nprotocol CoredataServiceProtocol {\\n\\n    func saveGenres(_ genres: [GenreViewModel]) -> Bool\\n    func fetchGenres() -> [GenreViewModel]\\n    func saveEpisode(_ episode: EpisodeViewModel, audio: Data) -> Bool\\n    func fetchEpisode(_ episode: EpisodeViewModel) -> EpisodeManagedObject?\\n    func fetchPodcast(_ id: Int) -> PodcastManagedObject?\\n    func bookmarkPodcast(_ podcast: PodcastViewModel) -> PodcastManagedObject?\\n    func deleteBookmarked(_ id: Int) -> PodcastManagedObject?\\n    // highlight-start\\n    func fetchAllPodcasts(bookmarkedOnly: Bool, onlyContainingEpisodes flag: Bool) -> [PodcastViewModel]\\n    // highlight-end\\n}\\n\")), mdx(\"h2\", null, \"Updating PreviewCoredataService\"), mdx(\"p\", null, \"Let's again update the preview coredata service as well.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23UpdatePreviewCoredataService1\\n// $$liquidcoderFilename-PreviewCoredataService\\n\\nimport CoreData\\n\\nclass PreviewCoredataService: CoredataServiceProtocol {\\n\\n    // highlight-start\\n    func fetchAllManagedPodcasts(bookmarkedOnly: Bool, onlyContainingEpisodes flag: Bool) -> [PodcastViewModel] {\\n        return PodcastViewModel.listPlaceholder\\n    }\\n    // highlight-end\\n\\n       ...\\n}\\n\")), mdx(\"h2\", null, \"Updating CoredataService\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson24UpdateCoredataService1\\n// $$liquidcoderFilename-CoredataService\\n\\nimport Foundation\\nimport CoreData\\n\\nclass CoredataService: CoredataServiceProtocol {\\n\\n    ...\\n\\n    func fetchAllPodcasts(bookmarkedOnly: Bool, onlyContainingEpisodes flag: Bool) -> [PodcastViewModel] {\\n\\n        let request: NSFetchRequest<PodcastManagedObject> = PodcastManagedObject.fetchRequest()\\n\\n        if bookmarkedOnly {\\n            request.predicate = NSPredicate(format: \\\"isBookmarked = %@\\\", \\\"1\\\")\\n        }\\n\\n        do {\\n            let results = try context.fetch(request)\\n\\n            // highlight-start\\n            if flag {\\n                return results\\n                    .filter { !$0.episodesArray.isEmpty }\\n                    .map { PodcastViewModel($0) }\\n            }\\n            // highlight-end\\n\\n            return results.map {\\n                PodcastViewModel($0)\\n            }           \\n        } catch let error {\\n            Log.error(error)\\n        }\\n\\n        return []\\n    }\\n\\n    ...\\n}\\n\")), mdx(\"p\", null, \"Here, we filter out only podcasts where episodes array is not empty, and then create viewmodels from them.\"), mdx(\"h2\", null, \"Updating Action\"), mdx(\"p\", null, \"Let's add an action to fetch podcast containing episodes.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23UpdateAction1\\n// $$liquidcoderFilename-Action\\n\\nimport Foundation\\n\\nenum Action {\\n    enum Api {\\n        case fetchPodcasts(_ term: String, limit: Int = 10)\\n        case fetchGenres(_ filename: String)\\n        case updatePodcast(_ podcast: PodcastViewModel)\\n    }\\n\\n    enum Coredata {\\n        case fetchGenres\\n        case saveGenres(_ genres: [GenreViewModel])\\n        case fetchPodcast(_ podcast: PodcastViewModel)\\n        case bookmark(_ podcast: PodcastViewModel)\\n        case deleteBookmarked(_ podcast: PodcastViewModel)\\n        case fetchBookmarkedPodcasts\\n        // highlight-start\\n        case fetchPodcastsWithEpisodes\\n        // highlight-end\\n    }\\n\\n    case api(_ action: Api)\\n    case coredata(_ action: Coredata)\\n}\\n\")), mdx(\"h2\", null, \"Updating AppState\"), mdx(\"p\", null, \"We also need to track the state of podcast containing episodes, so we create a property to do that. \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23UpdateAppState1\\n// $$liquidcoderFilename-AppState\\n\\nimport Foundation\\nimport SwiftUI\\n\\nenum AppState {\\n\\n    ...\\n\\n    struct  Api {\\n        var preferredPodcasts: [String: Result<[PodcastViewModel]>] = [:]\\n        var allGenres: Result<[GenreViewModel]> = .loading\\n        var podcast: Result<PodcastViewModel> = .loading\\n    }\\n\\n    struct Coredata {\\n        var savedGenres: [GenreViewModel] = []\\n        var savedPodcast: Result<PodcastViewModel> = .loading\\n        var bookmarkedPodcasts: Result<[PodcastViewModel]> = .loading\\n        // highlight-start\\n        var podcastsWithEpisodes: Result<[PodcastViewModel]> = .loading\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"h2\", null, \"Updating CoredataReducer\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson24CoredataReducer1\\n// $$liquidcoderFilename-CoredataReducer\\n\\nimport Foundation\\n\\nstruct CoredataReducer {\\n\\n    static func execute(action: Action.Coredata, state: inout AppState.Coredata, environment: AppEnvironment) {\\n\\n        switch action {\\n\\n        case .fetchGenres:\\n            state.savedGenres = environment.coredata.fetchGenres()\\n        case .saveGenres(let genres):\\n            _ = environment.coredata.saveGenres(genres)\\n        case .fetchPodcast(let podcast):\\n            let managedPodcast = environment.coredata.fetchPodcast(podcast.id)\\n            updateState(&state, savedPodcast: managedPodcast, existingPodcast: podcast)\\n        case .bookmark(let podcast):\\n            if let managedPodcast = environment.coredata.bookmarkPodcast(podcast) {\\n                updateState(&state, savedPodcast: managedPodcast, existingPodcast: podcast)\\n            }\\n        case .deleteBookmarked(let podcast):\\n            let managedPodcast = environment.coredata.deleteBookmarked(podcast.id)\\n            updateState(&state, savedPodcast: managedPodcast, existingPodcast: podcast)\\n        case .fetchBookmarkedPodcasts:\\n            // highlight-start\\n            let podcasts = environment.coredata.fetchAllPodcasts(bookmarkedOnly: true, onlyContainingEpisodes: false)\\n            // highlight-end\\n            state.bookmarkedPodcasts = .success(podcasts)\\n\\n        // highlight-start\\n        case .fetchPodcastsWithEpisodes:\\n            let podcasts = environment.coredata.fetchAllPodcasts(bookmarkedOnly: false, onlyContainingEpisodes: true)\\n            state.podcastsWithEpisodes = .success(podcasts)\\n        // highlight-end\\n        }\\n    }\\n\\n    ...\\n}\\n\")), mdx(\"p\", null, \"We first need to update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fetchBookmarkedPodcasts\"), \" block's \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fetchAllPodcasts\"), \" method, then we add \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fetchPodcastsWithEpisodes\"), \" which does the same thing as the above case, but update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"podcastsWithEpisodes\"), \".\"), mdx(\"h2\", null, \"DownloadedScreen\"), mdx(\"p\", null, \"Inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens\")), \" folder, create another folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Downloaded\")), \", and inside it add a new swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"DownloadedScreen\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS and macOS targets only.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson23DownloadedScreen2\\n// $$liquidcoderFilename-DownloadedScreen\\n\\nimport SwiftUI\\n\\nstruct DownloadedScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n\\n    // highlight-start\\n    private var grid: some View {\\n        LazyVGrid(columns: gridItems,\\n                  alignment: .leading,\\n                  spacing: 10,\\n                  content: { PodcastListContent(state: store.coredataState.bookmarkedPodcasts, fetchPodcasts: fetchPodcasts) })\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        ScrollView {\\n            // highlight-start\\n            grid\\n            // highlight-end\\n        }.navigationTitle(\\\"Downloaded\\\")\\n\\n    }\\n\\n    private func fetchPodcasts() {\\n        store.dispatch(.coredata(.fetchPodcastsWithEpisodes))\\n    }\\n}\\n\\nstruct DownloadedScreen_Previews: PreviewProvider {\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            DownloadedScreen()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            DownloadedScreen()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"p\", null, \"The code in this screen is almost identical with the one written in then \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"BookmarkedScreen\"), \", the only difference here is the action sent.\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#refactoring-podcastlistcontent","title":"Refactoring PodcastListContent"},{"url":"#refactoring-homemylibrarysection","title":"Refactoring HomeMyLibrarySection"},{"url":"#refactoring-podcastlistscreen","title":"Refactoring PodcastListScreen"},{"url":"#refactoring-bookmarkedscreen","title":"Refactoring BookmarkedScreen"},{"url":"#updating-failureview","title":"Updating FailureView"},{"url":"#updating-coredataserviceprotocol","title":"Updating CoredataServiceProtocol"},{"url":"#updating-previewcoredataservice","title":"Updating PreviewCoredataService"},{"url":"#updating-coredataservice","title":"Updating CoredataService"},{"url":"#updating-action","title":"Updating Action"},{"url":"#updating-appstate","title":"Updating AppState"},{"url":"#updating-coredatareducer","title":"Updating CoredataReducer"},{"url":"#downloadedscreen","title":"DownloadedScreen"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-03-26T09:07:27.106Z","isNewLayout":true,"previews":null,"slug":"lesson-25-search-and-main-screens","title":"Lesson #25: Search and Main Screens","updatedDate":"2021-03-26T00:00+00:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZ1DK2XZG22N0rpCdujEf6RmEp3VjFA17BAy","type":"zip","id":"970a104e-8a44-529f-b8af-83513f14d0dd","name":"Source code"}],"allChapters":null,"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25SearchFieldView1\\n// $$liquidcoderFilename-SearchFieldView\\n\\nimport SwiftUI\\n\\nstruct SearchFieldView: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct SearchFieldView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        SearchFieldView()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25SearchFieldView2\\n// $$liquidcoderFilename-SearchFieldView\\n\\nimport SwiftUI\\n\\nstruct SearchFieldView: View {\\n\\n    // highlight-start\\n    @Binding var searchTerm: String\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        HStack {\\n            Button(action: {}, label: {\\n                Image(systemName: \\\"magnifyingglass\\\")\\n                    .foregroundColor(.gray)\\n            })\\n                TextField(\\\"Search podcasts\\\", text: $searchTerm)\\n        }.padding(.vertical, 12)\\n        .padding(.horizontal)\\n        .background(Color.overlay)\\n        .cornerRadius(40)\\n        // highlight-end\\n    }\\n}\\n\\nstruct SearchFieldView_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        // highlight-start\\n        Group {\\n            SearchFieldView(searchTerm: .constant(\\\"\\\"), isSearching: .constant(true))\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            SearchFieldView(searchTerm: .constant(\\\"\\\"), isSearching: .constant(false))\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25SearchFieldView3\\n// $$liquidcoderFilename-SearchFieldView\\n\\nimport SwiftUI\\n\\nstruct SearchFieldView: View {\\n\\n    @Binding var searchTerm: String\\n\\n    // highlight-start\\n    @Binding var isSearching: Bool\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        HStack {\\n            Button(action: {\\n            // highlight-start\\n                if isSearching {\\n                    searchTerm = \\\"\\\"\\n                }   \\n            // highlight-end\\n            }, label: {\\n            // highlight-start\\n                Image(systemName: isSearching ? \\\"multiply\\\" : \\\"magnifyingglass\\\")\\n                    .foregroundColor(.gray)\\n            // highlight-end\\n            })\\n\\n            TextField(\\\"Search podcasts\\\", text: $searchTerm)\\n\\n        }.padding(.vertical, 12)\\n            .padding(.horizontal)\\n            .background(Color.overlay)\\n            .cornerRadius(40)\\n            // highlight-start\\n            .animation(.easeIn, value: isSearching)\\n            // highlight-end\\n\\n    }\\n}\\n\\nstruct SearchFieldView_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        Group {\\n            SearchFieldView(searchTerm: .constant(\\\"\\\"), isSearching: .constant(true))\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            SearchFieldView(searchTerm: .constant(\\\"\\\"), isSearching: .constant(false))\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"div\", {\n    id: \"Lesson25SearchScreen0\",\n    className: \"code-block\"\n  }, mdx(\"div\", {\n    className: \"end-result\"\n  }, mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1616670000/StreamCast/Lesson%2025/rnpaiktetrf1onymi1yk.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1616670000/StreamCast/Lesson%2025/rnpaiktetrf1onymi1yk.png\"\n  }))))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25SearchScreen1\\n// $$liquidcoderFilename-SearchScreen\\n\\nimport SwiftUI\\n\\nstruct SearchScreen: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct SearchScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        SearchScreen()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25SearchScreen2\\n// $$liquidcoderFilename-SearchScreen\\n\\nimport SwiftUI\\n\\nstruct SearchScreen: View {\\n\\n    // highlight-start\\n    @EnvironmentObject private var store: Store\\n    @State private var searchTerm = \\\"\\\"\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct SearchScreen_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n    // highlight-end\\n\\n    // highlight-start\\n    static var previews: some View {\\n        Group {\\n            SearchScreen()\\n                .preferredColorScheme(.dark)\\n            SearchScreen()\\n        }.environmentObject(store)\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25SearchScreen3\\n// $$liquidcoderFilename-SearchScreen\\n\\nimport SwiftUI\\n\\nstruct SearchScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n    @State private var searchTerm = \\\"\\\"\\n\\n    // highlight-start\\n    private var section: some View {\\n        Section(header: SearchFieldView(searchTerm: $searchTerm, isSearching: .constant(!searchTerm.isEmpty))) {\\n            PodcastListContent(state: getState(for: searchTerm), fetchPodcasts: fetchPodcasts)\\n        }\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n\\n    // highlight-start\\n    private func fetchPodcasts() {\\n        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {\\n            store.dispatch(.api(.fetchPodcasts(searchTerm, limit: 100)))\\n        }\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    private func getState(for term: String) -> AppState.Result<[PodcastViewModel]> {\\n        if let state = store.apiState.preferredPodcasts[term] {\\n            return state\\n        }\\n        return .loading\\n    }\\n    // highlight-end\\n}\\n\\nstruct SearchScreen_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            SearchScreen()\\n                .preferredColorScheme(.dark)\\n            SearchScreen()\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25SearchScreen4\\n// $$liquidcoderFilename-SearchScreen\\n\\nimport SwiftUI\\n\\nstruct SearchScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n    @State private var searchTerm = \\\"\\\"\\n\\n    private var section: some View {\\n        Section(header: SearchFieldView(searchTerm: $searchTerm, isSearching: .constant(!searchTerm.isEmpty))  ) {\\n            PodcastListContent(state: getState(for: searchTerm), fetchPodcasts: fetchPodcasts)\\n        }\\n    }\\n\\n    // highlight-start\\n    private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n\\n    private var grid: some View {\\n            LazyVGrid(columns: gridItems,\\n                      alignment: .leading,\\n                      spacing: 10,\\n                      pinnedViews: [.sectionHeaders],\\n                      content: { section })\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        ScrollView {\\n            grid\\n        }.navigationBarTitle(\\\"Search\\\", displayMode: searchTerm.isEmpty ? .large : .inline)\\n        // highlight-end\\n    }\\n\\n    private func fetchPodcasts() {\\n        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {\\n            store.dispatch(.api(.fetchPodcasts(searchTerm, limit: 100)))\\n        }\\n    }\\n\\n    private func getState(for term: String) -> AppState.Result<[PodcastViewModel]> {\\n        if let state = store.apiState.preferredPodcasts[term] {\\n            return state\\n        }\\n        return .loading\\n    }\\n}\\n\\nstruct SearchScreen_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            SearchScreen()\\n                .preferredColorScheme(.dark)\\n            SearchScreen()\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25MainScreen1\\n// $$liquidcoderFilename-MainScreen\\n\\nimport SwiftUI\\n\\nstruct MainScreen: View {\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct MainScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        MainScreen()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25MainScreen2\\n// $$liquidcoderFilename-MainScreen\\n\\nimport SwiftUI\\n\\nstruct MainScreen: View {\\n\\n    // highlight-start\\n    #if os(iOS)\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?\\n    #endif\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct MainScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            MainScreen()\\n                .preferredColorScheme(.dark)\\n            MainScreen()\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25MainScreen3\\n// $$liquidcoderFilename-MainScreen\\n\\nimport SwiftUI\\n\\nstruct MainScreen: View {\\n\\n    #if os(iOS)\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?\\n    #endif\\n\\n    var body: some View {\\n        NavigationView {\\n            // highlight-start\\n            if hSizeClass == .regular {\\n                Text(\\\"Sidebar menus\\\")\\n            } else {\\n                HomeScreen()\\n            }\\n            // highlight-end\\n        }\\n    }\\n}\\n\\nstruct MainScreen_Previews: PreviewProvider {\\n\\n        static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n        static var previews: some View {\\n        Group {\\n            MainScreen()\\n                .preferredColorScheme(.dark)\\n            MainScreen()\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25UpdateContentView1\\n// $$liquidcoderFilename-ContentView\\n\\nimport SwiftUI\\n\\nstruct ContentView: View {\\n\\n    @FetchRequest(\\n        sortDescriptors:\\n            [NSSortDescriptor(keyPath: \\\\GenreManagedObject.id, ascending: true)]\\n    ) private var genres: FetchedResults<GenreManagedObject>\\n\\n    var body: some View {\\n        if genres.isEmpty {\\n            GenreSelectionScreen()\\n        } else {\\n            // highlight-start\\n            MainScreen()\\n            // highlight-end\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25UpdateMenuItem1\\n// $$liquidcoderFilename-MenuItem\\n\\nimport Foundation\\n// highlight-start\\nimport SwiftUI\\n// highlight-end\\n\\nstruct MenuItem: Identifiable, Equatable, Hashable {\\n\\n    var id: String { \\n        name\\n    }\\n    var name = \\\"\\\"\\n    var icon = \\\"\\\"\\n\\n    static var placeholder: MenuItem {\\n        MenuItem(name: \\\"Item\\\", icon: \\\"circlebadge.fill\\\")\\n    }\\n\\n    static var all: [MenuItem] {\\n\\n        [\\n            // highlight-start\\n            MenuItem(name: \\\"Home\\\", icon: \\\"house.fill\\\"),\\n            // highlight-end\\n            MenuItem(name: \\\"Search\\\", icon: \\\"magnifyingglass.circle.fill\\\"),\\n            MenuItem(name: \\\"Downloaded\\\", icon: \\\"square.and.arrow.down.fill\\\"),\\n            MenuItem(name: \\\"Bookmarked\\\", icon: \\\"bookmark.fill\\\")\\n        ]\\n    }\\n\\n    // highlight-start\\n    @ViewBuilder\\n    var screen: some View {\\n\\n        if self.name == \\\"Home\\\" {\\n            HomeScreen()\\n        }\\n\\n        if self.name == \\\"Search\\\" {\\n            SearchScreen()\\n        }\\n\\n        if self.name == \\\"Downloaded\\\" {\\n            DownloadedScreen()\\n        }\\n\\n        if self.name == \\\"Bookmarked\\\" {\\n            BookmarkedScreen()\\n        }\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25HomeMenuList1\\n// $$liquidcoderFilename-HomeMenuList\\n\\nimport SwiftUI\\n\\nstruct HomeMenuList: View {\\n    var body: some View {\\n        ScrollView(.horizontal, showsIndicators: false) {\\n            HStack(spacing: 15) {\\n                // highlight-start\\n                ForEach(MenuItem.all.dropFirst()) { menu in\\n                    // highlight-end\\n                    MenuLink(menu: menu, destination: {\\n                        // highlight-start\\n                        menu.screen\\n                        // highlight-end\\n                    })\\n                }\\n            }.padding(.horizontal)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25UpdateHomeScreen1\\n// $$liquidcoderFilename-HomeScreen\\n\\nimport SwiftUI\\n\\nstruct HomeScreen: View {\\n\\n    // highlight-start\\n    #if os(iOS)\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?\\n    #endif\\n    // highlight-end\\n\\n    var body: some View {\\n        ScrollView {\\n            LazyVStack(spacing: 20) {\\n\\n                // highlight-start\\n                if hSizeClass == .compact {\\n                    HomeMenuList()\\n                }\\n                // highlight-end\\n                HomeGenresContent()\\n                HomeMyLibrarySection()\\n            }\\n                // highlight-start\\n        }.navigationTitle(\\\"Home\\\")\\n                // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25Sidebar1\\n// $$liquidcoderFilename-Sidebar\\n\\nimport SwiftUI\\n\\nstruct Sidebar: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct Sidebar_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Sidebar()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25Sidebar2\\n// $$liquidcoderFilename-Sidebar\\n\\nimport SwiftUI\\n\\nstruct Sidebar: View {\\n\\n    // highlight-start\\n    @State private var selected = MenuItem.all.first\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct Sidebar_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Sidebar()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25Sidebar3\\n// $$liquidcoderFilename-Sidebar\\n\\nimport SwiftUI\\n\\nstruct Sidebar: View {\\n\\n    @State private var selected = MenuItem.all.first\\n\\n    var body: some View {\\n        // highlight-start\\n        List {\\n            ForEach(MenuItem.all) { item in\\n                NavigationLink(destination: item.screen, tag: item, selection: $selected)  {\\n                    Label(\\n                        title: { Text(item.name) },\\n                        icon: { Image(systemName: item.icon) } )\\n                }\\n            }\\n        }.listStyle(SidebarListStyle())\\n         .navigationTitle(\\\"Menu\\\")\\n        // highlight-end\\n    }\\n}\\n\\nstruct Sidebar_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Sidebar()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\n// $$liquidcoderID-Lesson25MainScreen4\\n// $$liquidcoderFilename-MainScreen\\n\\nimport SwiftUI\\n\\nstruct MainScreen: View {\\n\\n    #if os(iOS)\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?\\n    #endif\\n\\n    var body: some View {\\n        NavigationView {\\n            if hSizeClass == .regular {\\n                // highlight-start\\n                Sidebar()\\n                // highlight-end\\n            } else {\\n                HomeScreen()\\n            }\\n        }\\n    }\\n}\\n\\nstruct MainScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            MainScreen()\\n                .preferredColorScheme(.dark)\\n            MainScreen()\\n        }\\n    }\\n}\\n\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25FixingProblems1\\n// $$liquidcoderFilename-MainScreen\\n\\nimport SwiftUI\\n\\nstruct MainScreen: View {\\n\\n    #if os(iOS)\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?\\n    #endif\\n\\n    var body: some View {\\n        NavigationView {\\n            if hSizeClass == .regular {\\n               Sidebar()\\n                // highlight-start\\n               HomeScreen()\\n                // highlight-end\\n            } else {\\n                HomeScreen()\\n            }\\n        }\\n    }\\n}\\n\\nstruct MainScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            MainScreen()\\n                .preferredColorScheme(.dark)\\n            MainScreen()\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25UpdateHomeMyLibrarySection1\\n// $$liquidcoderFilename-HomeMyLibrarySection\\n\\nimport SwiftUI\\n\\nstruct HomeMyLibrarySection: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    // highlight-start\\n    @SceneStorage(\\\"selectedGenre\\\") private var selectedGenre = \\\"\\\"\\n    // highlight-end\\n\\n    private var section: some View {\\n        Section(header: HomeMyLibrarySectionHeader(selected: $selectedGenre)) {\\n            PodcastListContent(state: getState(for: selectedGenre), fetchPodcasts: fetchPodcasts)\\n        }\\n    }\\n\\n    var body: some View {\\n\\n        LazyVGrid(columns: GridItems.shared,\\n                  alignment: .leading,\\n                  spacing: 10,\\n                  pinnedViews: [.sectionHeaders],\\n                  content: { section })\\n    }\\n\\n    private func fetchPodcasts() {\\n        store.dispatch(.api(.fetchPodcasts(selectedGenre, limit: 10)))\\n\\n    }\\n\\n    private func getState(for term: String) -> AppState.Result<[PodcastViewModel]> {\\n        if let state = store.apiState.preferredPodcasts[term] {\\n            return state\\n        }\\n        return .loading\\n    }\\n}\\n\\nstruct HomeMyLibrarySection_Previews: PreviewProvider {\\n    ...\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25UpdatePodcastDetailContent1\\n// $$liquidcoderFilename-PodcastDetailContent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailContent: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    @EnvironmentObject private var store: Store\\n\\n    // highlight-start\\n    #if os(iOS)\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?\\n    #endif\\n    // highlight-end\\n\\n    private var description: some View { ... }\\n\\n    private var allEpisodeLink: some View { ... }\\n\\n    private var bookmarkButton: some View { ... }\\n\\n    // highlight-start\\n    var compactContent: some View {\\n        GeometryReader { geometry in\\n           ScrollView(showsIndicators: false, content: {\\n               VStack {\\n                   createImage(size: geometry.size.width)\\n\\n                   VStack(alignment: .leading, spacing: 10) {\\n                       PodcastDetailHeaderComponent(podcast: podcast)\\n                       allEpisodeLink\\n                       Divider()\\n                       description\\n\\n                   }\\n               }\\n           }).navigationBarTitle(\\\"\\\", displayMode: .inline)\\n           .navigationBarItems(trailing: bookmarkButton)\\n       }\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    var regularContent: some View {\\n        EmptyView()\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        if hSizeClass == .compact {\\n             compactContent\\n        } else {\\n            regularContent\\n        }\\n        // highlight-end\\n    }\\n\\n    private func createImage(size: CGFloat, cornerRadius: CGFloat = 0) -> some View {\\n        SCImageView(podcast: podcast, size: size, cornerRadius: cornerRadius)\\n            .aspectRatio(contentMode: .fill)\\n    }\\n}\\n\\nstruct PodcastDetailContent_Previews: PreviewProvider {\\n\\n    static var previews: some View {\\n\\n        Group {\\n            PodcastDetailContent(podcast: PodcastViewModel.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PodcastDetailContent(podcast: PodcastViewModel.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25UpdatePodcastDetailContent2\\n// $$liquidcoderFilename-PodcastDetailContent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailContent: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    @EnvironmentObject private var store: Store\\n\\n    #if os(iOS)\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?\\n    #endif\\n\\n    private var description: some View { ... }\\n\\n    private var allEpisodeLink: some View { ... }\\n\\n    private var bookmarkButton: some View { ... }\\n\\n    var compactContent: some View { ... }\\n\\n    // highlight-start\\n    var regularContent: some View {\\n        HStack {\\n             GeometryReader { geometry in\\n                ScrollView(showsIndicators: false, content: {\\n                    VStack {\\n                        createImage(size: geometry.size.width, cornerRadius: 20)\\n\\n                        VStack(alignment: .leading, spacing: 10) {\\n                            PodcastDetailHeaderComponent(podcast: podcast)\\n                            description\\n\\n                        }\\n                    }\\n                })\\n            }\\n\\n            EpisodeListScreen(episodes: podcast.episodes)\\n        }.navigationBarTitle(\\\"\\\", displayMode: .inline)\\n        .padding(.horizontal)\\n        .toolbar {\\n           bookmarkButton\\n        }\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        if hSizeClass == .compact {\\n             compactContent\\n        } else {\\n                // highlight-start\\n            regularContent\\n                    // highlight-end\\n        }\\n    }\\n\\n        // highlight-start\\n    private func createImage(size: CGFloat, cornerRadius: CGFloat = 0) -> some View {\\n            // highlight-end\\n        SCImageView(podcast: podcast, size: size, cornerRadius: cornerRadius)\\n            .aspectRatio(contentMode: .fill)\\n    }\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson25Intro-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey ladies and gents,  we have reached our 25th lesson, this is a huge milestone. In the previous one, we created the download screen and refactored a couple of screens. In this one, we will create the search screen and the main screen that will put everything together.\")), mdx(\"div\", {\n    id: \"Lesson25SearchScreenPitch-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Let's start with the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SearchScreen\"), \". In this screen, we will need a search field that users will use to search for podcasts. So, let's create that search field view first.\")), mdx(\"div\", {\n    id: \"Lesson25SearchFieldView1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"SearchField View\"), mdx(\"p\", null, \"Create a new folder inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens\")), \" one named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Search\")), \", and inside that, create an \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Elements\")), \" folder in which you will create a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"SearchFieldView.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Just check the iOS target for this one as the native macOS and watchOS will be handled later.\"))), mdx(\"div\", {\n    id: \"Lesson25SearchFieldView2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"searchTerm\"), \" is a binding that will be provided from the calling site.\"), mdx(\"hr\", null), mdx(\"p\", null, \"In \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \", we add a button and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Textfield\"), \" inside an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HStack\"), \" container. The button will be used to to clear the textfield.\")), mdx(\"div\", {\n    id: \"Lesson25SearchFieldView3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We declare another binding property that will notify the view that searching is in progress, then based on that we will update the button's icon, and allow the user to clear the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"searchTerm\"), \" if \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isSearching\"), \" is \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"true\"), \".\"), mdx(\"hr\", null), mdx(\"p\", null, \"We also want to animate only when the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isSearching\"), \" property changes.\")), mdx(\"div\", {\n    id: \"Lesson25SearchScreen0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Search Screen\"), mdx(\"p\", null, \"This screen will be similar to the bookmark and downloaded ones with the addition of the search field view.\")), mdx(\"div\", {\n    id: \"Lesson25SearchScreen1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now, inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens \\u2192 Search\")), \" folder, add another swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"SearchScreen.\"))), mdx(\"div\", {\n    id: \"Lesson25SearchScreen2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We bring in the store to perform search with, and declare the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"searchTerm\"), \" as \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"State\"), \" property to be bound to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"searchTerm\"), \" parameter of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SearchFieldView\"))), mdx(\"div\", {\n    id: \"Lesson25SearchScreen3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Like I have just said, this screen is the same as the other 2 aforementioned with the exception of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SearchFieldView\"), \" added as a section header.\"), mdx(\"p\", null, \"We also use to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"searchTerm\"), \" to fetch podcasts inside an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"asyncAfter\"), \" block to wait for a small amount of time before fetching new podcasts. In our case, we are waiting for 1 second.\")), mdx(\"div\", {\n    id: \"Lesson25SearchScreen4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Last, we create the grid that we add inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Scrollview\"), \" in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \". We also want make the display mode of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"navigationBar\"), \" \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"large\"), \" if \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"searchTerm\"), \" is empty, otherwise we make it inline\")), mdx(\"div\", {\n    id: \"Lesson25MainScreen0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Main Screen\"), mdx(\"p\", null, \"The main screen will be the screen that will combine all the other screens and handle navigation between them.\")), mdx(\"div\", {\n    id: \"Lesson25MainScreen1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now, create another folder in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens\")), \" one, and name it \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Main\")), \", and inside it add a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"MainScreen.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Just check the iOS target for this one.\"))), mdx(\"div\", {\n    id: \"Lesson25MainScreen2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Let's first bring in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"horizontalSizeClass\"), \" from the swiftUI \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Environment\"), \". \"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Read more about size classes \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/adaptivity-and-layout/\"\n  }), \"here\"), \" and \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://github.com/renaudjenny/SwiftUI-with-Size-Classes\"\n  }), \"here\")))), mdx(\"div\", {\n    id: \"Lesson25MainScreen3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We will only have the sidebar menus on larger screens, otherwise we will show the single column screen  for iPhones in portrait mode.\")), mdx(\"div\", {\n    id: \"Lesson25UpdateContentView1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the ContentView\"), mdx(\"p\", null, \"Before we continue, replace whatever is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ContentView\"), \" with the following.\")), mdx(\"div\", {\n    id: \"Lesson25UpdateMenuItem1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the MenuItem\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"First, we import \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"swiftUI\"), \", then conforms the struct to the  \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Hashable\"), \" protocol to be used with the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"NavigationLink\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Next, we replace the first menu item in the array with the home one. \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"And Last, we create a property that returns an appropriate screen for the current \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"menuItem\"), \".\"))), mdx(\"div\", {\n    id: \"Lesson25HomeMenuList1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating HomeMenuList\"), mdx(\"p\", null, \"We don't want to show the first (Home) menu on the iPhone or smaller screens, so we drop it.  Then for the destination, we return the screen for the current menu.\")), mdx(\"div\", {\n    id: \"Lesson25UpdateHomeScreen1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating HomeScreen\"), mdx(\"p\", null, \"On the home screen, we only want to show the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeMenuList\"), \" component when the app is running on the iPhone or smaller devices hence the check for \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"compact\"), \" rather than \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"regular\"), \" like we did in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"MainScreen\"), \".\")), mdx(\"div\", {\n    id: \"Lesson25Sidebar1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Sidebar\"), mdx(\"p\", null, \"Create a new folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Components\")), \" inside of the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Main\")), \" one, and add a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Sidebar.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS target only.\"))), mdx(\"div\", {\n    id: \"Lesson25Sidebar2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Here, we declare the selected menu item as \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"State\"), \" to be bound to the selection parameter of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NavigationLink\"), \".\")), mdx(\"div\", {\n    id: \"Lesson25Sidebar3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Then, inside \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \", we create a list containing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NavigationLink\"), \"s for all menu items, then we give the list a style of type \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SidebarListStyle\"), \" which is a built-in style for sidebar menus.\")), mdx(\"div\", {\n    id: \"Lesson25MainScreen4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Having everything we need, we can now replace the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" with the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Sidebar\"), \" view. \"), mdx(\"hr\", null), mdx(\"p\", null, \"Run the app on an iPad, and you should see the sidebar view. \")), mdx(\"div\", {\n    id: \"Lesson25FixingProblems0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Fixing Problems\"), mdx(\"p\", null, \"We have 2 problems when running the app on iPad.\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"If you run the app while the iPad is in portrait, you will be greeted with a blank screen until you click on the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"back\"), \" button.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"When you go back to the home screen after clicking the other menu items, you will find that the list of podcasts under \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"HomeMyLibrarySection\"), \" is missing.\")), mdx(\"p\", null, \"So let's fix them up.\")), mdx(\"div\", {\n    id: \"Lesson25FixingProblems1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The first problem is a swiftUI bug if I am not mistaken, so that fix is temporary until the bug is fixed or I find a better way of doing it.\")), mdx(\"div\", {\n    id: \"Lesson25FixingProblems2-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating HomeMyLibrarySection\"), mdx(\"p\", null, \"The second problem was caused by a reset of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"selectedGenre\"), \" property in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeMyLibrarySection\"), \" view. Every time, you leave the home screen, the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"selectedGenre\"), \" would be reset to empty string which is not a valid genre, hence no podcasts fetched.\")), mdx(\"div\", {\n    id: \"Lesson25UpdateHomeMyLibrarySection1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"So, to fix it we need to persist the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"selectedGenre\"), \" using the new \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SceneStorage\"), \" from swiftUI.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"You use SceneStorage when you need automatic state restoration of the value. SceneStorage works very similar to State, except its initial value is restored by the system if it was previously saved, and the value is\\xB7 shared with other SceneStorage variables in the same scene.\"))), mdx(\"div\", {\n    id: \"Lesson25UpdateHomeMyLibrarySection2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"After you've done that,  run the app and try to navigate to other screens. Everything should work as expected.\"), mdx(\"p\", null, \"The last view we need to fix is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailContent\"), \" which needs to have 2 columns on the iPad as well.\")), mdx(\"div\", {\n    id: \"Lesson25UpdatePodcastDetailContent1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Updating the PodcastDetailContent\"), mdx(\"p\", null, \"We bring in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"horizontalSizeClass\"), \" from the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Environment\"), \".\"), mdx(\"p\", null, \"Then, we put the content in  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \" in its own variable that will be displayed on iPhones portrait or narrow spaces.\")), mdx(\"div\", {\n    id: \"Lesson25UpdatePodcastDetailContent2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next, we create the regular content that will be displayed on iPads or larger spaces.\"), mdx(\"p\", null, \"Here, we align the details horizontally with the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeListScreen\"), \" , and update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createImage\"), \" to include a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cornerRadius\"), \" parameter.\")), mdx(\"div\", {\n    id: \"Lesson25Outro-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And this brings our 25th lesson to a close guys. In the next one, we will start working on the podcast player which will take several lessons to complete. Happy coding!\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#searchfield-view","title":"SearchField View"},{"url":"#search-screen","title":"Search Screen"},{"url":"#main-screen","title":"Main Screen"},{"url":"#updating-the-contentview","title":"Updating the ContentView"},{"url":"#updating-the-menuitem","title":"Updating the MenuItem"},{"url":"#updating-homemenulist","title":"Updating HomeMenuList"},{"url":"#updating-homescreen","title":"Updating HomeScreen"},{"url":"#sidebar","title":"Sidebar"},{"url":"#fixing-problems","title":"Fixing Problems"},{"url":"#updating-homemylibrarysection","title":"Updating HomeMyLibrarySection"},{"url":"#updating-the-podcastdetailcontent","title":"Updating the PodcastDetailContent"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey ladies and gents,  we have reached our 25th lesson, this is a huge milestone. In the previous one, we created the download screen and refactored a couple of screens. In this one, we will create the search screen and the main screen that will put everything together.\"), mdx(\"p\", null, \"Let's start with the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SearchScreen\"), \". In this screen, we will need a search field that users will use to search for podcasts. So, let's create that search field view first.\"), mdx(\"h2\", null, \"SearchField View\"), mdx(\"p\", null, \"Create a new folder inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens\")), \" one named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Search\")), \", and inside that, create an \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Elements\")), \" folder in which you will create a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"SearchFieldView.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Just check the iOS target for this one as the native macOS and watchOS will be handled later.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25SearchFieldView2\\n// $$liquidcoderFilename-SearchFieldView\\n\\nimport SwiftUI\\n\\nstruct SearchFieldView: View {\\n\\n    // highlight-start\\n    @Binding var searchTerm: String\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        HStack {\\n            Button(action: {}, label: {\\n                Image(systemName: \\\"magnifyingglass\\\")\\n                    .foregroundColor(.gray)\\n            })\\n                TextField(\\\"Search podcasts\\\", text: $searchTerm)\\n        }.padding(.vertical, 12)\\n        .padding(.horizontal)\\n        .background(Color.overlay)\\n        .cornerRadius(40)\\n        // highlight-end\\n    }\\n}\\n\\nstruct SearchFieldView_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        // highlight-start\\n        Group {\\n            SearchFieldView(searchTerm: .constant(\\\"\\\"), isSearching: .constant(true))\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            SearchFieldView(searchTerm: .constant(\\\"\\\"), isSearching: .constant(false))\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"searchTerm\"), \" is a binding that will be provided from the calling site.\"), mdx(\"p\", null, \"In \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \", we add a button and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Textfield\"), \" inside an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HStack\"), \" container. The button will be used to to clear the textfield.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25SearchFieldView3\\n// $$liquidcoderFilename-SearchFieldView\\n\\nimport SwiftUI\\n\\nstruct SearchFieldView: View {\\n\\n    @Binding var searchTerm: String\\n\\n    // highlight-start\\n    @Binding var isSearching: Bool\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        HStack {\\n\\n            // highlight-start\\n            Button(action: {\\n                            if isSearching {\\n                    searchTerm = \\\"\\\"\\n                }   \\n                        }, label: {\\n                Image(systemName: isSearching ? \\\"multiply\\\" : \\\"magnifyingglass\\\")\\n                    .foregroundColor(.gray)\\n            })\\n            // highlight-end\\n\\n            TextField(\\\"Search podcasts\\\", text: $searchTerm)\\n\\n        }.padding(.vertical, 12)\\n        .padding(.horizontal)\\n        .background(Color.overlay)\\n        .cornerRadius(40)\\n        // highlight-start\\n        .animation(.easeIn, value: isSearching)\\n        // highlight-end\\n\\n    }\\n}\\n\\nstruct SearchFieldView_Previews: PreviewProvider {\\n    static var previews: some View {\\n\\n        Group {\\n            SearchFieldView(searchTerm: .constant(\\\"\\\"), isSearching: .constant(true))\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            SearchFieldView(searchTerm: .constant(\\\"\\\"), isSearching: .constant(false))\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"We declare another binding property that will notify the view that searching is in progress, then based on that we will update the button's icon, and allow the user to clear the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"searchTerm\"), \" if \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isSearching\"), \" is \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"true\"), \".\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1616669999/StreamCast/Lesson%2025/whzvqverdhnhqmyovqls.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1616669999/StreamCast/Lesson%2025/whzvqverdhnhqmyovqls.png\"\n  }))), mdx(\"hr\", null), mdx(\"p\", null, \"We also want to animate only when the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isSearching\"), \" property changes.\"), mdx(\"h2\", null, \"Search Screen\"), mdx(\"p\", null, \"This screen will be similar to the bookmark and downloaded ones with the addition of the search field view.\"), mdx(\"p\", null, \"Now, inside the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens \\u2192 Search\")), \" folder, add another swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"SearchScreen.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25SearchScreen2\\n// $$liquidcoderFilename-SearchScreen\\n\\nimport SwiftUI\\n\\nstruct SearchScreen: View {\\n\\n    // highlight-start\\n    @EnvironmentObject private var store: Store\\n    @State private var searchTerm = \\\"\\\"\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct SearchScreen_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n    // highlight-end\\n\\n    // highlight-start\\n    static var previews: some View {\\n        Group {\\n            SearchScreen()\\n                .preferredColorScheme(.dark)\\n            SearchScreen()\\n        }.environmentObject(store)\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"p\", null, \"We bring in the store to perform search with, and declare the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"searchTerm\"), \" as \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"State\"), \" property to be bound to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"searchTerm\"), \" parameter of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SearchFieldView\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25SearchScreen3\\n// $$liquidcoderFilename-SearchScreen\\n\\nimport SwiftUI\\n\\nstruct SearchScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n    @State private var searchTerm = \\\"\\\"\\n\\n    // highlight-start\\n    private var section: some View {\\n        Section(header: SearchFieldView(searchTerm: $searchTerm, isSearching: .constant(!searchTerm.isEmpty))  ) {\\n            PodcastListContent(state: getState(for: searchTerm), fetchPodcasts: fetchPodcasts)\\n        }\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n\\n    // highlight-start\\n    private func fetchPodcasts() {\\n        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {\\n            store.dispatch(.api(.fetchPodcasts(searchTerm, limit: 100)))\\n        }\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    private func getState(for term: String) -> AppState.Result<[PodcastViewModel]> {\\n        if let state = store.apiState.preferredPodcasts[term] {\\n            return state\\n        }\\n        return .loading\\n    }\\n    // highlight-end\\n}\\n\\nstruct SearchScreen_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            SearchScreen()\\n                .preferredColorScheme(.dark)\\n            SearchScreen()\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"p\", null, \"Like I have just said, this screen is the same as the other 2 aforementioned with the exception of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SearchFieldView\"), \" added as a section header.\"), mdx(\"p\", null, \"We also use to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"searchTerm\"), \" to fetch podcasts inside an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"asyncAfter\"), \" block to wait for a small amount of time before fetching new podcasts. In our case, we are waiting for 1 second.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25SearchScreen4\\n// $$liquidcoderFilename-SearchScreen\\n\\nimport SwiftUI\\n\\nstruct SearchScreen: View {\\n\\n    @EnvironmentObject private var store: Store\\n    @State private var searchTerm = \\\"\\\"\\n\\n    private var section: some View {\\n        Section(header: SearchFieldView(searchTerm: $searchTerm, isSearching: .constant(!searchTerm.isEmpty))  ) {\\n            PodcastListContent(state: getState(for: searchTerm), fetchPodcasts: fetchPodcasts)\\n        }\\n    }\\n\\n    // highlight-start\\n    private let gridItems: [GridItem] = Array(repeating: .init(.adaptive(minimum: 350), spacing: 20), count: 1)\\n\\n    private var grid: some View {\\n            LazyVGrid(columns: gridItems,\\n                      alignment: .leading,\\n                      spacing: 10,\\n                      pinnedViews: [.sectionHeaders],\\n                      content: { section })\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        ScrollView {\\n            grid\\n        }.navigationBarTitle(\\\"Search\\\", displayMode: searchTerm.isEmpty ? .large : .inline)\\n        // highlight-end\\n    }\\n\\n    private func fetchPodcasts() {\\n        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {\\n            store.dispatch(.api(.fetchPodcasts(searchTerm, limit: 100)))\\n        }\\n    }\\n\\n    private func getState(for term: String) -> AppState.Result<[PodcastViewModel]> {\\n        if let state = store.apiState.preferredPodcasts[term] {\\n            return state\\n        }\\n        return .loading\\n    }\\n}\\n\\nstruct SearchScreen_Previews: PreviewProvider {\\n\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n    static var previews: some View {\\n        Group {\\n            SearchScreen()\\n                .preferredColorScheme(.dark)\\n            SearchScreen()\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"p\", null, \"Last, we create the grid that we add inside the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Scrollview\"), \" in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \". We also want make the display mode of the navigation bar large if \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"searchTerm\"), \" is empty, otherwise we make it inline\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1616670000/StreamCast/Lesson%2025/rnpaiktetrf1onymi1yk.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1616670000/StreamCast/Lesson%2025/rnpaiktetrf1onymi1yk.png\"\n  }))), mdx(\"h2\", null, \"Main Screen\"), mdx(\"p\", null, \"The main screen will be the screen that will combine all the other screens and handle navigation between them.\"), mdx(\"p\", null, \"Now, create another folder in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Screens\")), \" one, and name it \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Main\")), \", and inside it add a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"MainScreen.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Just check the iOS target for this one.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25MainScreen2\\n// $$liquidcoderFilename-MainScreen\\n\\nimport SwiftUI\\n\\nstruct MainScreen: View {\\n\\n    // highlight-start\\n    #if os(iOS)\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?\\n    #endif\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct MainScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            MainScreen()\\n                .preferredColorScheme(.dark)\\n            MainScreen()\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"Let's first bring in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"horizontalSizeClass\"), \" from the swiftUI \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Environment\"), \". \"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Read more about size classes \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/adaptivity-and-layout/\"\n  }), \"here\"), \" and \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://github.com/renaudjenny/SwiftUI-with-Size-Classes\"\n  }), \"here\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25MainScreen3\\n// $$liquidcoderFilename-MainScreen\\n\\nimport SwiftUI\\n\\nstruct MainScreen: View {\\n\\n    #if os(iOS)\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?\\n    #endif\\n\\n    var body: some View {\\n        NavigationView {\\n            // highlight-start\\n            if hSizeClass == .regular {\\n                Text(\\\"Sidebar menus\\\")\\n            } else {\\n                HomeScreen()\\n            }\\n            // highlight-end\\n        }\\n    }\\n}\\n\\nstruct MainScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            MainScreen()\\n                .preferredColorScheme(.dark)\\n            MainScreen()\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"We will only have the sidebar menus on larger screens, otherwise we will show the single column screen  for iPhones in portrait mode.\"), mdx(\"h2\", null, \"Updating the ContentView\"), mdx(\"p\", null, \"Before we continue, replace whatever is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ContentView\"), \" with the following.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25UpdateContentView1\\n// $$liquidcoderFilename-ContentView\\n\\nimport SwiftUI\\n\\nstruct ContentView: View {\\n\\n    @FetchRequest(\\n        sortDescriptors:\\n            [NSSortDescriptor(keyPath: \\\\GenreManagedObject.id, ascending: true)]\\n    ) private var genres: FetchedResults<GenreManagedObject>\\n\\n    var body: some View {\\n        if genres.isEmpty {\\n            GenreSelectionScreen()\\n        } else {\\n            // highlight-start\\n            MainScreen()\\n            // highlight-end\\n        }\\n    }\\n}\\n\")), mdx(\"h2\", null, \"Updating the MenuItem\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"First, we import swiftUI, then conforms the struct to the  \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Hashable\"), \" protocol to be used with the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"NavigationLink\")), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Next, we are replace the first menu item in the array with the home one. \"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"And Last, we create a property that returns an appropriate screen for the current \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"menuItem\"), \".\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25UpdateMenuItem1\\n// $$liquidcoderFilename-MenuItem\\n\\nimport Foundation\\n// highlight-start\\nimport SwiftUI\\n// highlight-end\\n\\n// highlight-start\\nstruct MenuItem: Identifiable, Equatable, Hashable {\\n// highlight-end    \\n\\n    var id: String { \\n        name\\n    }\\n    var name = \\\"\\\"\\n    var icon = \\\"\\\"\\n\\n    static var placeholder: MenuItem {\\n        MenuItem(name: \\\"Item\\\", icon: \\\"circlebadge.fill\\\")\\n    }\\n\\n    static var all: [MenuItem] {\\n\\n        [\\n            // highlight-start\\n            MenuItem(name: \\\"Home\\\", icon: \\\"house.fill\\\"),\\n            // highlight-end\\n            MenuItem(name: \\\"Search\\\", icon: \\\"magnifyingglass.circle.fill\\\"),\\n            MenuItem(name: \\\"Downloaded\\\", icon: \\\"square.and.arrow.down.fill\\\"),\\n            MenuItem(name: \\\"Bookmarked\\\", icon: \\\"bookmark.fill\\\")\\n        ]\\n    }\\n\\n    // highlight-start\\n    @ViewBuilder\\n    var screen: some View {\\n\\n        if self.name == \\\"Home\\\" {\\n            HomeScreen()\\n        }\\n\\n        if self.name == \\\"Search\\\" {\\n            SearchScreen()\\n        }\\n\\n        if self.name == \\\"Downloaded\\\" {\\n            DownloadedScreen()\\n        }\\n\\n        if self.name == \\\"Bookmarked\\\" {\\n            BookmarkedScreen()\\n        }\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"h2\", null, \"Updating HomeMenuList\"), mdx(\"p\", null, \"We don't want to show the first (Home) menu on the iPhone or smaller screens, so we drop it.  Then for the destination, we return the screen for the current menu.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25HomeMenuList1\\n// $$liquidcoderFilename-HomeMenuList\\n\\nimport SwiftUI\\n\\nstruct HomeMenuList: View {\\n    var body: some View {\\n        ScrollView(.horizontal, showsIndicators: false) {\\n            HStack(spacing: 15) {\\n                // highlight-start\\n                ForEach(MenuItem.all.dropFirst()) { menu in\\n                    // highlight-end\\n                    MenuLink(menu: menu, destination: {\\n                        // highlight-start\\n                        menu.screen\\n                        // highlight-start\\n                    })\\n                }\\n            }.padding(.horizontal)\\n        }\\n    }\\n}\\n\")), mdx(\"h2\", null, \"Updating HomeScreen\"), mdx(\"p\", null, \"On the home screen, we only want to show the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeMenuList\"), \" component when the app is running on the iPhone or smaller devices hence the check for \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"compact\"), \" rather than \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"regular\"), \" like we did in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"MainScreen\"), \".\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25UpdateHomeScreen1\\n// $$liquidcoderFilename-HomeScreen\\n\\nimport SwiftUI\\n\\nstruct HomeScreen: View {\\n\\n    // highlight-start\\n    #if os(iOS)\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?\\n    #endif\\n    // highlight-end\\n\\n    var body: some View {\\n        ScrollView {\\n            LazyVStack(spacing: 20) {\\n\\n                // highlight-start\\n                if hSizeClass == .compact {\\n                    HomeMenuList()\\n                }\\n                // highlight-end\\n                HomeGenresContent()\\n                HomeMyLibrarySection()\\n            }\\n                // highlight-start\\n        }.navigationTitle(\\\"Home\\\")\\n                // highlight-end\\n    }\\n}\\n\")), mdx(\"h2\", null, \"Sidebar\"), mdx(\"p\", null, \"Create a new folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Components\")), \" inside of the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Main\")), \" one, and add a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Sidebar.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS target only.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25Sidebar2\\n// $$liquidcoderFilename-Sidebar\\n\\nimport SwiftUI\\n\\nstruct Sidebar: View {\\n\\n    // highlight-start\\n    @State private var selected = MenuItem.all.first\\n    // highlight-end\\n\\n    var body: some View {\\n        // highlight-start\\n        List {\\n            ForEach(MenuItem.all) { item in\\n                NavigationLink(destination: item.screen, tag: item, selection: $selected)  {\\n                    Label(\\n                        title: { Text(item.name) },\\n                        icon: { Image(systemName: item.icon) } )\\n                }\\n            }\\n        }.listStyle(SidebarListStyle())\\n.navigationTitle(\\\"Menu\\\")\\n        // highlight-end\\n    }\\n}\\n\\nstruct Sidebar_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Sidebar()\\n    }\\n}\\n\")), mdx(\"p\", null, \"Here, we declare the selected menu item as \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"State\"), \" to be bound to the selection parameter of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NavigationLink\"), \".\"), mdx(\"hr\", null), mdx(\"p\", null, \"Then, inside \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \", we create a list containing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"NavigationLink\"), \"s for all menu items, then we give the list a style of type \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SidebarListStyle\"), \" which is a built-in style for sidebar menus.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1616670000/StreamCast/Lesson%2025/myulrod72tnpmvu0vff2.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1616670000/StreamCast/Lesson%2025/myulrod72tnpmvu0vff2.png\"\n  }))), mdx(\"p\", null, \"Having everything we need, we can now update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" with the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Sidebar\"), \" view. \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25MainScreen4\\n// $$liquidcoderFilename-MainScreen\\n\\nimport SwiftUI\\n\\nstruct MainScreen: View {\\n\\n    #if os(iOS)\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?\\n    #endif\\n\\n    var body: some View {\\n        NavigationView {\\n            if hSizeClass == .regular {\\n                // highlight-start\\n               Sidebar()\\n                // highlight-end\\n            } else {\\n                HomeScreen()\\n            }\\n        }\\n    }\\n}\\n\\nstruct MainScreen_Previews: PreviewProvider {\\n\\n        static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n\\n        static var previews: some View {\\n        Group {\\n            MainScreen()\\n                .preferredColorScheme(.dark)\\n            MainScreen()\\n        }.environmentObject(store)\\n    }\\n}\\n\")), mdx(\"p\", null, \"Now you can run the app on an iPad, and you should see the sidebar view. \"), mdx(\"h2\", null, \"Fixing Problems\"), mdx(\"p\", null, \"We have 2 problems when running the app on iPad.\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"If you run the app while the iPad is in portrait, you will be greeted with a blank screen until you click on the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"back\"), \" button.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"When you go back to the home screen after clicking the other menu items, you will find that the list of podcasts under \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"HomeMyLibrarySection\"), \" is missing.\")), mdx(\"p\", null, \"So let's fix them up.  The first problem is a swiftUI bug if I am not mistaken, so that fix is temporary until the bug is fixed of I find a better way of doing it.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25FixingProblems1\\n// $$liquidcoderFilename-MainScreen\\n\\nimport SwiftUI\\n\\nstruct MainScreen: View {\\n\\n    #if os(iOS)\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?\\n    #endif\\n\\n    var body: some View {\\n        NavigationView {\\n            if hSizeClass == .regular {\\n               Sidebar()\\n                // highlight-start\\n                             HomeScreen()\\n                // highlight-end\\n            } else {\\n                HomeScreen()\\n            }\\n        }\\n    }\\n}\\n\\nstruct MainScreen_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            MainScreen()\\n                .preferredColorScheme(.dark)\\n            MainScreen()\\n        }\\n    }\\n}\\n\")), mdx(\"h2\", null, \"Updating HomeMyLibrarySection\"), mdx(\"p\", null, \"The second problem was caused by a reset of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"selectedGenre\"), \" property in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HomeMyLibrarySection\"), \" view. Every time, you leave the home screen, the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"selectedGenre\"), \" would be reset to empty string which is not a valid genre, hence no podcasts fetched.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25UpdateHomeMyLibrarySection1\\n// $$liquidcoderFilename-HomeMyLibrarySection\\n\\nimport SwiftUI\\n\\nstruct HomeMyLibrarySection: View {\\n\\n    @EnvironmentObject private var store: Store\\n\\n    // highlight-start\\n    @SceneStorage(\\\"selectedGenre\\\") private var selectedGenre = \\\"\\\"\\n    // highlight-end\\n\\n    ...\\n}\\n\\nstruct HomeMyLibrarySection_Previews: PreviewProvider {\\n    ...\\n}\\n\")), mdx(\"p\", null, \"So, to fix it we need to persist the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"selectedGenre\"), \" using the new \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SceneStorage\"), \" from swiftUI.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"You use SceneStorage when you need automatic state restoration of the value. SceneStorage works very similar to State, except its initial value is restored by the system if it was previously saved, and the value is\\xB7 shared with other SceneStorage variables in the same scene.\")), mdx(\"p\", null, \"After you've done that,  run the app and try to navigate to other screens. Everything should work as expected.\"), mdx(\"p\", null, \"The last view we need to fix is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PodcastDetailContent\"), \" which needs to have 2 columns on the iPad as well.\"), mdx(\"h2\", null, \"Updating the PodcastDetailContent\"), mdx(\"p\", null, \"We bring in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"horizontalSizeClass\"), \" from the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Environment\"), \".\"), mdx(\"p\", null, \"Then, we put the content in the body in its own variable that will be displayed on iPhones portrait or narrow spaces.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25UpdatePodcastDetailContent1\\n// $$liquidcoderFilename-PodcastDetailContent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailContent: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    @EnvironmentObject private var store: Store\\n\\n    // highlight-start\\n    #if os(iOS)\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?\\n    #endif\\n    // highlight-end\\n\\n    private var description: some View { ... }\\n\\n    private var allEpisodeLink: some View { ... }\\n\\n    private var bookmarkButton: some View { ... }\\n\\n    // highlight-start\\n    var compactContent: some View {\\n        GeometryReader { geometry in\\n           ScrollView(showsIndicators: false, content: {\\n               VStack {\\n                   createImage(size: geometry.size.width)\\n\\n                   VStack(alignment: .leading, spacing: 10) {\\n                       PodcastDetailHeaderComponent(podcast: podcast)\\n                       allEpisodeLink\\n                       Divider()\\n                       description\\n\\n                   }\\n               }\\n           }).navigationBarTitle(\\\"\\\", displayMode: .inline)\\n           .navigationBarItems(trailing: bookmarkButton)\\n       }\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    var regularContent: some View {\\n        EmptyView()\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        if hSizeClass == .compact {\\n             compactContent\\n        } else {\\n            regularContent\\n        }\\n        // highlight-end\\n    }\\n\\n    private func createImage(size: CGFloat, cornerRadius: CGFloat = 0) -> some View {\\n        SCImageView(podcast: podcast, size: size, cornerRadius: cornerRadius)\\n            .aspectRatio(contentMode: .fill)\\n    }\\n}\\n\")), mdx(\"p\", null, \"Next, we create the regular content that will be displayed on iPads or larger spaces.\"), mdx(\"p\", null, \"Here, we align the details horizontally with the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeListScreen\"), \" , and update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createImage\"), \" to include a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cornerRadius\"), \" parameter.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25UpdatePodcastDetailContent2\\n// $$liquidcoderFilename-PodcastDetailContent\\n\\nimport SwiftUI\\n\\nstruct PodcastDetailContent: View {\\n\\n    var podcast: PodcastViewModel\\n\\n    @EnvironmentObject private var store: Store\\n\\n    #if os(iOS)\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?\\n    #endif\\n\\n    private var description: some View { ... }\\n\\n    private var allEpisodeLink: some View { ... }\\n\\n    private var bookmarkButton: some View { ... }\\n\\n    var compactContent: some View { ... }\\n\\n    // highlight-start\\n    var regularContent: some View {\\n        HStack {\\n             GeometryReader { geometry in\\n                ScrollView(showsIndicators: false, content: {\\n                    VStack {\\n                        createImage(size: geometry.size.width, cornerRadius: 20)\\n\\n                        VStack(alignment: .leading, spacing: 10) {\\n                            PodcastDetailHeaderComponent(podcast: podcast)\\n                            description\\n\\n                        }\\n                    }\\n                })\\n            }\\n\\n            EpisodeListScreen(episodes: podcast.episodes)\\n        }.navigationBarTitle(\\\"\\\", displayMode: .inline)\\n        .padding(.horizontal)\\n        .toolbar {\\n           bookmarkButton\\n        }\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        if hSizeClass == .compact {\\n             compactContent\\n        } else {\\n            regularContent\\n        }\\n        // highlight-end\\n    }\\n\\n        // highlight-start\\n    private func createImage(size: CGFloat, cornerRadius: CGFloat = 0) -> some View {\\n        SCImageView(podcast: podcast, size: size, cornerRadius: cornerRadius)\\n            .aspectRatio(contentMode: .fill)\\n    }\\n        // highlight-end\\n}\\n\")), mdx(\"p\", null, \"And this brings our 25th lesson to a close guys. In the next one, we will start working on the podcast player which will take several lessons to complete. Happy coding!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#searchfield-view","title":"SearchField View"},{"url":"#search-screen","title":"Search Screen"},{"url":"#main-screen","title":"Main Screen"},{"url":"#updating-the-contentview","title":"Updating the ContentView"},{"url":"#updating-the-menuitem","title":"Updating the MenuItem"},{"url":"#updating-homemenulist","title":"Updating HomeMenuList"},{"url":"#updating-homescreen","title":"Updating HomeScreen"},{"url":"#sidebar","title":"Sidebar"},{"url":"#fixing-problems","title":"Fixing Problems"},{"url":"#updating-homemylibrarysection","title":"Updating HomeMyLibrarySection"},{"url":"#updating-the-podcastdetailcontent","title":"Updating the PodcastDetailContent"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-03-29T08:40:00.001Z","isNewLayout":true,"previews":null,"slug":"lesson-26-streamcast-player","title":"Lesson #26: Streamcast Player","updatedDate":"2021-03-29T00:00+01:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZVSr2XZxMbnKhF0bBjR0g2ORYgHK0ANLAY7","type":"zip","id":"1d3d6476-15df-5443-b55a-f1964e732ba6","name":"Source code"}],"allChapters":null,"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer1\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\n// highlight-start\\nimport AVFoundation\\nimport MediaPlayer\\n// highlight-end\\n\\nclass SCPlayer: NSObject {\\n\\n    // highlight-start\\n    var player = AVPlayer()\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer2\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    var player = AVPlayer()\\n\\n    // highlight-start\\n    private var timeObserverToken: Any?\\n    private var playerItemStatusObserver: NSKeyValueObservation?\\n    // highlight-end\\n\\n    // highlight-start\\n    var onPlayerStart: ((Double) -> Void) = { _ in }\\n    var onTimeChange: ((Double) -> Void) = {_ in }\\n    var onTogglePlay: ((Bool) -> Void) = {_ in }\\n    var onFailure: ((String) -> Void) = { _ in }\\n    // highlight-end\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer3\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    var player = AVPlayer()\\n\\n    private var timeObserverToken: Any?\\n    private var playerItemStatusObserver: NSKeyValueObservation?\\n\\n    var onPlayerStart: ((Double) -> Void) = { _ in }\\n    var onTimeChange: ((Double) -> Void) = {_ in }\\n    var onTogglePlay: ((Bool) -> Void) = {_ in }\\n    var onFailure: ((String) -> Void) = { _ in }\\n\\n    // highlight-start\\n    func play(url: URL) {\\n\\n        let asset = AVAsset(url: url)\\n\\n    }\\n    // highlight-end\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer4\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    var player = AVPlayer()\\n\\n    private var timeObserverToken: Any?\\n    private var playerItemStatusObserver: NSKeyValueObservation?\\n\\n    var onPlayerStart: ((Double) -> Void) = { _ in }\\n    var onTimeChange: ((Double) -> Void) = {_ in }\\n    var onTogglePlay: ((Bool) -> Void) = {_ in }\\n    var onFailure: ((String) -> Void) = { _ in }\\n\\n    func play(url: URL) {\\n\\n        let asset = AVAsset(url: url)\\n        // highlight-start\\n        let playerItem = AVPlayerItem(asset: asset)\\n        player.replaceCurrentItem(with: playerItem)\\n        // highlight-end\\n\\n    }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer5\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    ...\\n\\n    func play(url: URL) {\\n\\n        let asset = AVAsset(url: url)\\n        let playerItem = AVPlayerItem(asset: asset)\\n        player.replaceCurrentItem(with: playerItem)\\n        // highlight-start\\n        prepareAsset(asset)\\n        // highlight-end\\n\\n    }\\n\\n    // highlight-start\\n    private func prepareAsset(_ asset: AVAsset) {\\n\\n        let playableKeys =  [ \\\"playable\\\", \\\"hasProtectedContent\\\" ]\\n\\n        asset.loadValuesAsynchronously(forKeys: playableKeys) { [weak self] in\\n            guard self != nil else { return }\\n\\n        }\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer6\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    ...\\n\\n    func play(url: URL) {\\n\\n        let asset = AVAsset(url: url)\\n        let playerItem = AVPlayerItem(asset: asset)\\n        player.replaceCurrentItem(with: playerItem)\\n        prepareAsset(asset)\\n    }\\n\\n    private func prepareAsset(_ asset: AVAsset) {\\n        let playableKeys =  [ \\\"playable\\\", \\\"hasProtectedContent\\\" ]\\n        asset.loadValuesAsynchronously(forKeys: playableKeys) { [weak self] in\\n                guard let self = self else { return }\\n\\n    // highlight-start\\n                if self.validateValues(for: playableKeys, asset: asset) {\\n                    self.setupObservers()\\n                } \\n    // highlight-end\\n        }\\n    }\\n} \\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer7\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    ...\\n\\n    override init() {\\n        super.init()\\n        player = AVPlayer()\\n    }\\n\\n    func play(url: URL) {\\n\\n        let asset = AVAsset(url: url)\\n        let playerItem = AVPlayerItem(asset: asset)\\n        player.replaceCurrentItem(with: playerItem)\\n        prepareAsset(asset)\\n    }\\n\\n    // highlight-start\\n    private func validateValues(for keys: [String], asset: AVAsset) -> Bool {\\n        return true\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    private func setupObservers() {\\n\\n    }\\n    // highlight-end\\n\\n    private func prepareAsset(_ asset: AVAsset) {\\n        let playableKeys =  [ \\\"playable\\\", \\\"hasProtectedContent\\\" ]\\n        asset.loadValuesAsynchronously(forKeys: playableKeys) { [weak self] in\\n            guard let self = self else { return }\\n\\n                if self.validateValues(for: playableKeys, asset: asset) {\\n                    self.setupObservers()\\n                } \\n                }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer8\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    ...\\n\\n    func play(url: URL) {\\n\\n        let asset = AVAsset(url: url)\\n        let playerItem = AVPlayerItem(asset: asset)\\n        player.replaceCurrentItem(with: playerItem)\\n        prepareAsset(asset)\\n    }\\n\\n    private func validateValues(for keys: [String], asset: AVAsset) -> Bool {\\n\\n                // highlight-start\\n        for key in keys {\\n            var error: NSError?\\n            if asset.statusOfValue(forKey: key, error: &error) == .failed {\\n                self.onFailure(\\\"The media failed to load key: \\\\(key)\\\")\\n                return false\\n            }\\n        }\\n        // highlight-end\\n\\n        // highlight-start\\n        #if os(watchOS)\\n        if !asset.isPlayable {\\n            self.onFailure(\\\"Sorry, the media is not playable.\\\")\\n            return false\\n\\n        }\\n        #else\\n        if !asset.isPlayable || asset.hasProtectedContent {\\n            self.onFailure(\\\"Sorry, the media is not playable.\\\")\\n            return false\\n\\n        }\\n        #endif\\n        // highlight-end\\n\\n        return true\\n    }\\n\\n    private func setupObservers() {\\n\\n    }\\n\\n    private func prepareAsset(_ asset: AVAsset) {\\n        let playableKeys =  [ \\\"playable\\\", \\\"hasProtectedContent\\\" ]\\n        asset.loadValuesAsynchronously(forKeys: playableKeys) { [weak self] in\\n\\n                guard let self = self else { return }\\n\\n                if self.validateValues(for: playableKeys, asset: asset) {\\n                    self.setupObservers()\\n                } \\n                }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer9\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    ...\\n\\n    func play(url: URL) {\\n\\n        let asset = AVAsset(url: url)\\n        let playerItem = AVPlayerItem(asset: asset)\\n        player.replaceCurrentItem(with: playerItem)\\n        prepareAsset(asset)\\n    }\\n\\n    private func validateValues(for keys: [String], asset: AVAsset) -> Bool {\\n        ...\\n    }\\n\\n    private func setupObservers() {\\n\\n        // highlight-start\\n        let interval = CMTime(value: 1, timescale: 1)\\n        timeObserverToken = player.addPeriodicTimeObserver(forInterval: interval,\\n                                                           queue: .main) { [unowned self] time in\\n            // Update progress\\n\\n        }\\n        // highlight-end\\n\\n        // highlight-start\\n        playerItemStatusObserver = player.observe(\\\\AVPlayer.currentItem?.status, options: [.new, .initial]) { [unowned self] _, _ in\\n\\n                // Update player status\\n\\n        }\\n        // highlight-end\\n    }\\n\\n    private func prepareAsset(_ asset: AVAsset) {\\n        let playableKeys =  [ \\\"playable\\\", \\\"hasProtectedContent\\\" ]\\n        asset.loadValuesAsynchronously(forKeys: playableKeys) { [weak self] in\\n            guard let self = self else { return }\\n\\n                        if self.validateValues(for: playableKeys, asset: asset) {\\n                    self.setupObservers()\\n                } \\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer10\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    ...\\n\\n    func play(url: URL) {\\n        ...\\n    }\\n\\n    private func validateValues(for keys: [String], asset: AVAsset) -> Bool {\\n            ...\\n    }\\n\\n    // highlight-start\\n    private func updateProgress(with time: CMTime)  {\\n        self.onTimeChange(time.seconds)\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    private func updatePlayerStatus() {\\n        guard let currentItem = player.currentItem else { return }\\n        switch currentItem.status {\\n        case .readyToPlay: break\\n        case .failed: break\\n        default: break\\n\\n        }\\n    }\\n    // highlight-end\\n\\n    private func setupObservers() {\\n\\n        let interval = CMTime(value: 1, timescale: 1)\\n        timeObserverToken = player.addPeriodicTimeObserver(forInterval: interval,\\n                                                           queue: .main) { [unowned self] time in\\n            // highlight-start\\n            self.updateProgress(with: time)\\n            // highlight-end\\n\\n        }\\n\\n        playerItemStatusObserver = player.observe(\\\\AVPlayer.currentItem?.status, options: [.new, .initial]) { [unowned self] _, _ in\\n            // highlight-start\\n            DispatchQueue.main.async {\\n                self.updatePlayerStatus()\\n            }\\n            // highlight-end\\n        }\\n    }\\n\\n    private func prepareAsset(_ asset: AVAsset) {\\n        let playableKeys =  [ \\\"playable\\\", \\\"hasProtectedContent\\\" ]\\n        asset.loadValuesAsynchronously(forKeys: playableKeys) { [weak self] in\\n            guard let self = self else { return }\\n\\n                        if self.validateValues(for: playableKeys, asset: asset) {\\n                    self.setupObservers()\\n                } \\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer11\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    ...\\n\\n    func play(url: URL) {\\n\\n        let asset = AVAsset(url: url)\\n        let playerItem = AVPlayerItem(asset: asset)\\n        player.replaceCurrentItem(with: playerItem)\\n        prepareAsset(asset)\\n    }\\n\\n    private func validateValues(for keys: [String], asset: AVAsset) -> Bool {\\n        ...\\n    }\\n\\n    private func updateProgress(with time: CMTime)  {\\n        self.onTimeChange(time.seconds)\\n    }\\n\\n    private func updatePlayerStatus() {\\n        guard let currentItem = player.currentItem else { return }\\n        switch currentItem.status {\\n        case .readyToPlay:\\n        // highlight-start\\n            self.player.play()\\n            self.onTogglePlay(true)\\n            self.onPlayerStart(currentItem.asset.duration.seconds)\\n        // highlight-end\\n        case .failed:\\n        // highlight-start\\n            self.onFailure(\\\"Sorry, the media is not playable.\\\")\\n        // highlight-end\\n        default:\\n        // highlight-start\\n            Log.info(\\\"Unknown status\\\")\\n        // highlight-end\\n        }\\n    }\\n\\n    private func setupObservers() {\\n\\n        let interval = CMTime(value: 1, timescale: 1)\\n        timeObserverToken = player.addPeriodicTimeObserver(forInterval: interval,\\n                                                           queue: .main) { [unowned self] time in\\n            self.updateProgress(with: time)\\n\\n        }\\n\\n        playerItemStatusObserver = player.observe(\\\\AVPlayer.currentItem?.status, options: [.new, .initial]) { [unowned self] _, _ in\\n            DispatchQueue.main.async {\\n                self.updatePlayerStatus()\\n            }\\n        }\\n    }\\n\\n    private func prepareAsset(_ asset: AVAsset) {\\n        let playableKeys =  [ \\\"playable\\\", \\\"hasProtectedContent\\\" ]\\n        asset.loadValuesAsynchronously(forKeys: playableKeys) { [weak self] in\\n            guard let self = self else { return }\\n\\n                        if self.validateValues(for: playableKeys, asset: asset) {\\n                    self.setupObservers()\\n                } \\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer12\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    ...\\n\\n    func play(url: URL) {\\n\\n        let asset = AVAsset(url: url)\\n        let playerItem = AVPlayerItem(asset: asset)\\n        player.replaceCurrentItem(with: playerItem)\\n        prepareAsset(asset)\\n    }\\n\\n    // highlight-start\\n    func fastForward()  {\\n\\n        var time: TimeInterval = player.currentTime().seconds\\n        time += 15.0\\n        if player.currentItem?.currentTime() == player.currentItem?.duration {\\n            return\\n        }\\n\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    func fastBackward()  {\\n\\n        var time: TimeInterval = player.currentTime().seconds\\n        time -= 15.0 // Go back by 15 seconds\\n        if player.currentItem?.currentTime() == .zero {\\n            return\\n        }\\n\\n    }\\n    // highlight-end\\n\\n    private func validateValues(for keys: [String], asset: AVAsset) -> Bool {\\n        ...\\n    }\\n\\n    private func updateProgress(with time: CMTime)  {\\n        self.onTimeChange(time.seconds)\\n    }\\n\\n    private func updatePlayerStatus() {\\n        ...\\n    }\\n\\n    private func setupObservers() {\\n        ...\\n    }\\n\\n    private func prepareAsset(_ asset: AVAsset) {\\n        ...\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer13\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    ...\\n\\n    func play(url: URL) {\\n\\n        let asset = AVAsset(url: url)\\n        let playerItem = AVPlayerItem(asset: asset)\\n        player.replaceCurrentItem(with: playerItem)\\n        prepareAsset(asset)\\n    }\\n\\n    // highlight-start\\n    func seek(to value: Double) {\\n        let time = CMTime(seconds: value, preferredTimescale: 1000)\\n        player.seek(to: time)\\n    }\\n    // highlight-end\\n\\n    func fastForward()  {\\n\\n        var time: TimeInterval = player.currentTime().seconds\\n        time += 5.0\\n        if player.currentItem?.currentTime() == player.currentItem?.duration {\\n            return\\n        }\\n\\n        // highlight-start\\n        seek(to: time)\\n        // highlight-start\\n    }\\n\\n    func fastBackward()  {\\n\\n        var time: TimeInterval = player.currentTime().seconds\\n        time -= 5.0 // Go back by 5 seconds\\n        if player.currentItem?.currentTime() == .zero {\\n            return\\n        }\\n\\n        // highlight-start\\n        seek(to: time)\\n        // highlight-start\\n    }\\n\\n    private func validateValues(for keys: [String], asset: AVAsset) -> Bool {\\n        ...\\n    }\\n\\n    private func updateProgress(with time: CMTime)  {\\n        self.onTimeChange(time.seconds)\\n    }\\n\\n    private func updatePlayerStatus() { ... }\\n\\n    private func setupObservers() { ... }\\n\\n    private func prepareAsset(_ asset: AVAsset) { ... }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer14\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    ...\\n\\n    // highlight-start\\n    func togglePlay()  {\\n        switch self.player.timeControlStatus {\\n        case .paused, .waitingToPlayAtSpecifiedRate:\\n            player.play()\\n            onTogglePlay(true)\\n        case .playing:\\n            player.pause()\\n            onTogglePlay(false)\\n        default:\\n            player.pause()\\n            onTogglePlay(false)\\n        }\\n    }\\n    // highlight-end\\n\\n    func seek(to value: Double) { ... }\\n\\n    func fastForward() { ... }\\n\\n    func fastBackward() { ... }\\n\\n    private func validateValues(for keys: [String], asset: AVAsset) -> Bool {\\n        ...\\n    }\\n\\n    private func updateProgress(with time: CMTime) {\\n        self.onTimeChange(time.seconds)\\n    }\\n\\n    private func updatePlayerStatus() {\\n        ...\\n    }\\n\\n    private func setupObservers() {\\n        ...\\n    }\\n\\n    private func prepareAsset(_ asset: AVAsset) {\\n        ...\\n    }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer15\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    ...\\n\\n    func seek(to value: Double) { ... }\\n\\n    func fastForward()  { ... }\\n\\n    func fastBackward()  { ... }\\n\\n    // highlight-start\\n    private func removePeriodicTimeObserver() {\\n        if let timeObserverToken = timeObserverToken {\\n            player.removeTimeObserver(timeObserverToken)\\n            self.timeObserverToken = nil\\n        }\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    deinit {\\n        removePeriodicTimeObserver()\\n    }\\n    // highlight-end\\n\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson26Intro-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey guys, welcome back to lesson #26. In the previous one, we finally handled the iPad layout, and in this one we will start working on the player feature. Let's get started.\")), mdx(\"div\", {\n    id: \"Lesson26SCPlayer0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"StreamCast Player\"), mdx(\"p\", null, \"This class will be a wrapper around the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AVFoundation\"), \"'s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AVPlayer\"), \". \"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Shared\")), \" folder, add swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"SCPlayer.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS, macOS and watchOS targets.\"))), mdx(\"div\", {\n    id: \"Lesson26SCPlayer1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We first bring in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AVFoundation\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"MediaPlayer\"), \" frameworks.\"), mdx(\"p\", null, \"Then in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"class\"), \", we declare the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"player\"), \" and initialise it inline.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Learn more about the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AVPlayer\"), \" class \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://developer.apple.com/documentation/avfoundation/avplayer\"\n  }), \"here\")))), mdx(\"div\", {\n    id: \"Lesson26SCPlayer2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The first 2 objects will be used to keep track of objects returned by various observers that we will use later on.\"), mdx(\"p\", null, \"The 4 closures will be used to send values to the calling site when different events occur.\")), mdx(\"div\", {\n    id: \"Lesson26SCPlayer3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"play\"), \" method, we initialise the asset using the provided url.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Learn more about the AVAsset class \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://developer.apple.com/documentation/avfoundation/avasset\"\n  }), \"here\")))), mdx(\"div\", {\n    id: \"Lesson26SCPlayer4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Having the asset, we then create the playerItem from it that we start playing using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AVPlayer\"), \"'s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"replaceCurrentItem\"), \" method.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Learn more about the AVPlayerItem class \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://developer.apple.com/documentation/avfoundation/avplayeritem\"\n  }), \"here\")))), mdx(\"div\", {\n    id: \"Lesson26SCPlayer41-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"After we have replaced the current item, we then need to perform a few checks to make sure  we can play the asset provided.\")), mdx(\"div\", {\n    id: \"Lesson26SCPlayer5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"prepareAsset\"), \" will load values for the provided keys from the asset to check their status. We will be able to play the asset if the status is not \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cancelled\"), \" or \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"failed\"))), mdx(\"div\", {\n    id: \"Lesson26SCPlayer6-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Here, we validate values for the provided keys for the given asset. \"), mdx(\"p\", null, \"Let's create the missing methods next.\")), mdx(\"div\", {\n    id: \"Lesson26SCPlayer7-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The first method  receives keys and the asset, and returns a boolean indicating whether the validation succeeded or not, and the second will be used to observe events from the player\")), mdx(\"div\", {\n    id: \"Lesson26SCPlayer8-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We first check if the status of at least one value is \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"failed\"), \", then terminate the process if that's the case, otherwise, we proceed to checking the individual keys, and return appropriate values. \"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"watchOS does not \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"hasProtectedContent\"), \" hence the use of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"*os*\"), \" condition\"))), mdx(\"div\", {\n    id: \"Lesson26SCPlayer9-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We create a 1 second  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"interval\"), \" that we use to add a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"addPeriodicTimeObserver\"), \" to the player which will make the block run every second. We will update the progress in a moment.\"), mdx(\"p\", null, \"We also observe the current playing item's status to know when the player is ready to play.\")), mdx(\"div\", {\n    id: \"Lesson26SCPlayer10-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We update the progress by calling \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"updateProgress\"), \"  created above, passing in the time.\"), mdx(\"hr\", null), mdx(\"p\", null, \"Next, we call the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"updatePlayerStatus\"), \" to get updates for the currently playing asset.\")), mdx(\"div\", {\n    id: \"Lesson26SCPlayer11-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"If the item is read to play, we then call the play method on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"player\"), \" object to start playing, otherwise we send a failure message to the calling site.\")), mdx(\"div\", {\n    id: \"Lesson26SCPlayer12-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"These 2 methods first check whether it's possible to fast backward or forward based on the current time.\")), mdx(\"div\", {\n    id: \"Lesson26SCPlayer13-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"seek\"), \", we calculate the time based on the value provided, then call the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"seek\"), \" on the player object passing in the calculated time.\")), mdx(\"div\", {\n    id: \"Lesson26SCPlayer14-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"togglePlay\"), \" method will update the player status  between between \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"playing\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"paused\"), \".\")), mdx(\"div\", {\n    id: \"Lesson26SCPlayer15-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Last, we remove the observers when this class is reinitialised to avoid memory related issues.\")), mdx(\"div\", {\n    id: \"Lesson26Outro-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And this bring lesson #26 to a close, this was just a player, in the next lesson we will build the manager that will be a bridge between the player with the view. See you then, happy coding!!!\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#streamcast-player","title":"StreamCast Player"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey guys, welcome back to lesson #26. In the previous one, we finally handled the iPad layout, and in this one we will start working on the player feature. Let's get started.\"), mdx(\"h2\", null, \"StreamCast Player\"), mdx(\"p\", null, \"This class will be a wrapper around the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AVFoundation\"), \"'s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AVPlayer\"), \". \"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Shared\")), \" folder, add swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"SCPlayer.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS, macOS and watchOS targets.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer1\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\n// highlight-start\\nimport AVFoundation\\nimport MediaPlayer\\n// highlight-end\\n\\nclass SCPlayer: NSObject {\\n\\n    // highlight-start\\n    var player = AVPlayer()\\n    // highlight-end\\n}\\n\")), mdx(\"p\", null, \"We first bring in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AVFoundation\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"MediaPlayer\"), \" frameworks.\"), mdx(\"p\", null, \"Then in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"class\"), \", we declare the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"player\"), \" and initialise it inline.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Learn more about the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AVPlayer\"), \" class \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://developer.apple.com/documentation/avfoundation/avplayer\"\n  }), \"here\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer2\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    var player = AVPlayer()\\n\\n    // highlight-start\\n    private var timeObserverToken: Any?\\n    private var playerItemStatusObserver: NSKeyValueObservation?\\n    // highlight-end\\n\\n    // highlight-start\\n    var onPlayerStart: ((Double) -> Void) = { _ in }\\n    var onTimeChange: ((Double) -> Void) = {_ in }\\n    var onTogglePlay: ((Bool) -> Void) = {_ in }\\n    var onFailure: ((String) -> Void) = { _ in }\\n    // highlight-end\\n\\n}\\n\")), mdx(\"p\", null, \"The first 2 objects will be used to keep track of objects returned by various observers that we will use later on.\"), mdx(\"p\", null, \"The 4 closures will be used to send values to the calling site when different events occur.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer3\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    var player = AVPlayer()\\n\\n    private var timeObserverToken: Any?\\n    private var playerItemStatusObserver: NSKeyValueObservation?\\n\\n    var onPlayerStart: ((Double) -> Void) = { _ in }\\n    var onTimeChange: ((Double) -> Void) = {_ in }\\n    var onTogglePlay: ((Bool) -> Void) = {_ in }\\n    var onFailure: ((String) -> Void) = { _ in }\\n\\n    // highlight-start\\n    func play(url: URL) {\\n\\n        let asset = AVAsset(url: url)\\n\\n    }\\n    // highlight-end\\n\\n}\\n\")), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"play\"), \" method, we initialise the asset using the provided url.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Learn more about the AVAsset class \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://developer.apple.com/documentation/avfoundation/avasset\"\n  }), \"here\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer4\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    var player = AVPlayer()\\n\\n    private var timeObserverToken: Any?\\n    private var playerItemStatusObserver: NSKeyValueObservation?\\n\\n    var onPlayerStart: ((Double) -> Void) = { _ in }\\n    var onTimeChange: ((Double) -> Void) = {_ in }\\n    var onTogglePlay: ((Bool) -> Void) = {_ in }\\n    var onFailure: ((String) -> Void) = { _ in }\\n\\n    func play(url: URL) {\\n\\n        let asset = AVAsset(url: url)\\n        // highlight-start\\n        let playerItem = AVPlayerItem(asset: asset)\\n        player.replaceCurrentItem(with: playerItem)\\n        // highlight-end\\n\\n    }\\n\\n}\\n\")), mdx(\"p\", null, \"Having the asset, we then create the playerItem from it that we start playing using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AVPlayer\"), \"'s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"replaceCurrentItem\"), \" method.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Learn more about the AVPlayerItem class \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://developer.apple.com/documentation/avfoundation/avplayeritem\"\n  }), \"here\"))), mdx(\"p\", null, \"After we have replaced the current item, we then need to perform a few checks to make sure  we can play the asset provided.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer5\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    ...\\n\\n    func play(url: URL) {\\n\\n        let asset = AVAsset(url: url)\\n        let playerItem = AVPlayerItem(asset: asset)\\n        player.replaceCurrentItem(with: playerItem)\\n        // highlight-start\\n        prepareAsset(asset)\\n        // highlight-end\\n\\n    }\\n\\n    // highlight-start\\n    private func prepareAsset(_ asset: AVAsset) {\\n        let playableKeys =  [ \\\"playable\\\", \\\"hasProtectedContent\\\" ]\\n        asset.loadValuesAsynchronously(forKeys: playableKeys) { [weak self] in\\n            guard self != nil else { return }\\n\\n        }\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"prepareAsset\"), \" will load values for the provided keys from the asset to check their status. We will be able to play the asset if the status is not \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cancelled\"), \" or \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"failed\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer6\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    ...\\n\\n    func play(url: URL) {\\n\\n        let asset = AVAsset(url: url)\\n        let playerItem = AVPlayerItem(asset: asset)\\n        player.replaceCurrentItem(with: playerItem)\\n        prepareAsset(asset)\\n    }\\n\\n    private func prepareAsset(_ asset: AVAsset) {\\n        let playableKeys =  [ \\\"playable\\\", \\\"hasProtectedContent\\\" ]\\n        asset.loadValuesAsynchronously(forKeys: playableKeys) { [weak self] in\\n            guard let self = self else { return }\\n\\n                        if self.validateValues(for: playableKeys, asset: asset) {\\n                    self.setupObservers()\\n                } \\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"Here, we validate values for the provided keys for the given asset. \"), mdx(\"p\", null, \"Let's create the missing methods next.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer7\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    ...\\n\\n    func play(url: URL) {\\n\\n        let asset = AVAsset(url: url)\\n        let playerItem = AVPlayerItem(asset: asset)\\n        player.replaceCurrentItem(with: playerItem)\\n        prepareAsset(asset)\\n    }\\n\\n    // highlight-start\\n    private func validateValues(for keys: [String], asset: AVAsset) -> Bool {\\n        return true\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    private func setupObservers() {\\n\\n    }\\n    // highlight-end\\n\\n    private func prepareAsset(_ asset: AVAsset) {\\n        let playableKeys =  [ \\\"playable\\\", \\\"hasProtectedContent\\\" ]\\n        asset.loadValuesAsynchronously(forKeys: playableKeys) { [weak self] in\\n            guard let self = self else { return }\\n\\n                        if self.validateValues(for: playableKeys, asset: asset) {\\n                    self.setupObservers()\\n                } \\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"The first method  receives keys and the asset, and returns a boolean indicating whether the validation succeeded or not, and the second will be used to observe events from the player\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer8\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    ...\\n\\n    func play(url: URL) {\\n\\n        let asset = AVAsset(url: url)\\n        let playerItem = AVPlayerItem(asset: asset)\\n        player.replaceCurrentItem(with: playerItem)\\n        prepareAsset(asset)\\n    }\\n\\n    private func validateValues(for keys: [String], asset: AVAsset) -> Bool {\\n\\n                // highlight-start\\n        for key in keys {\\n            var error: NSError?\\n            if asset.statusOfValue(forKey: key, error: &error) == .failed {\\n                self.onFailure(\\\"The media failed to load key: \\\\(key)\\\")\\n                return false\\n            }\\n        }\\n        // highlight-end\\n\\n        // highlight-start\\n        #if os(watchOS)\\n        if !asset.isPlayable {\\n            self.onFailure(\\\"Sorry, the media is not playable.\\\")\\n            return false\\n\\n        }\\n        #else\\n        if !asset.isPlayable || asset.hasProtectedContent {\\n            self.onFailure(\\\"Sorry, the media is not playable.\\\")\\n            return false\\n\\n        }\\n        #endif\\n        // highlight-end\\n\\n        return true\\n    }\\n\\n    private func setupObservers() {\\n\\n    }\\n\\n    private func prepareAsset(_ asset: AVAsset) {\\n        let playableKeys =  [ \\\"playable\\\", \\\"hasProtectedContent\\\" ]\\n        asset.loadValuesAsynchronously(forKeys: playableKeys) { [weak self] in\\n\\n                if self.validateValues(for: playableKeys, asset: asset) {\\n            self.setupObservers()\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"We first check if the status of at least one value is \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"failed\"), \", then terminate the process if that's the case, otherwise, we proceed to checking the individual keys, and return appropriate values. \"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"watchOS does not \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"hasProtectedContent\"), \" hence the use of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"*os*\"), \" condition\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer9\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    ...\\n\\n    func play(url: URL) {\\n\\n        let asset = AVAsset(url: url)\\n        let playerItem = AVPlayerItem(asset: asset)\\n        player.replaceCurrentItem(with: playerItem)\\n        prepareAsset(asset)\\n    }\\n\\n    private func validateValues(for keys: [String], asset: AVAsset) -> Bool {\\n        ...\\n    }\\n\\n    private func setupObservers() {\\n\\n        // highlight-start\\n        let interval = CMTime(value: 1, timescale: 1)\\n        timeObserverToken = player.addPeriodicTimeObserver(forInterval: interval,\\n                                                           queue: .main) { [unowned self] time in\\n            // Update progress\\n        }\\n        // highlight-end\\n\\n        // highlight-start\\n        playerItemStatusObserver = player.observe(\\\\AVPlayer.currentItem?.status, options: [.new, .initial]) { [unowned self] _, _ in\\n                // Update player status\\n        }\\n        // highlight-end\\n    }\\n\\n    private func prepareAsset(_ asset: AVAsset) {\\n        let playableKeys =  [ \\\"playable\\\", \\\"hasProtectedContent\\\" ]\\n        asset.loadValuesAsynchronously(forKeys: playableKeys) { [weak self] in\\n            guard let self = self else { return }\\n\\n                        if self.validateValues(for: playableKeys, asset: asset) {\\n                    self.setupObservers()\\n                } \\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"We create a 1 second  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"interval\"), \" that we use to add a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"addPeriodicTimeObserver\"), \" to the player which will make the block run every second. We will update the progress in a moment.\"), mdx(\"p\", null, \"We also observe the current playing item's status to know when the player is ready to play.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer10\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    ...\\n\\n    func play(url: URL) {\\n        ...\\n    }\\n\\n    private func validateValues(for keys: [String], asset: AVAsset) -> Bool {\\n            ...\\n    }\\n\\n    // highlight-start\\n    private func updateProgress(with time: CMTime)  {\\n        self.onTimeChange(time.seconds)\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    private func updatePlayerStatus() {\\n        guard let currentItem = player.currentItem else { return }\\n        switch currentItem.status {\\n        case .readyToPlay: break\\n        case .failed: break\\n        default: break\\n\\n        }\\n    }\\n    // highlight-end\\n\\n    private func setupObservers() {\\n\\n        let interval = CMTime(value: 1, timescale: 1)\\n        timeObserverToken = player.addPeriodicTimeObserver(forInterval: interval,\\n                                                           queue: .main) { [unowned self] time in\\n            // highlight-start\\n            self.updateProgress(with: time)\\n            // highlight-end\\n\\n        }\\n\\n        playerItemStatusObserver = player.observe(\\\\AVPlayer.currentItem?.status, options: [.new, .initial]) { [unowned self] _, _ in\\n            // highlight-start\\n            DispatchQueue.main.async {\\n                self.updatePlayerStatus()\\n            }\\n            // highlight-end\\n        }\\n    }\\n\\n    private func prepareAsset(_ asset: AVAsset) {\\n        let playableKeys =  [ \\\"playable\\\", \\\"hasProtectedContent\\\" ]\\n        asset.loadValuesAsynchronously(forKeys: playableKeys) { [weak self] in\\n            guard let self = self else { return }\\n\\n            if self.validateValues(for: playableKeys, asset: asset) {\\n                    self.setupObservers()\\n                }\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"We update the progress by calling \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"updateProgress\"), \"  created above, passing in the time.\"), mdx(\"p\", null, \"Next, we call the  \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"updatePlayerStatus\"), \" to get updates for the currently playing asset.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer11\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    ...\\n\\n    func play(url: URL) {\\n\\n        let asset = AVAsset(url: url)\\n        let playerItem = AVPlayerItem(asset: asset)\\n        player.replaceCurrentItem(with: playerItem)\\n        prepareAsset(asset)\\n    }\\n\\n    private func validateValues(for keys: [String], asset: AVAsset) -> Bool {\\n        ...\\n    }\\n\\n    private func updateProgress(with time: CMTime)  {\\n        self.onTimeChange(time.seconds)\\n    }\\n\\n    private func updatePlayerStatus() {\\n        guard let currentItem = player.currentItem else { return }\\n        switch currentItem.status {\\n        case .readyToPlay:\\n        // highlight-start\\n            self.player.play()\\n            self.onTogglePlay(true)\\n            self.onPlayerStart(currentItem.asset.duration.seconds)\\n        // highlight-end\\n        case .failed:\\n        // highlight-start\\n            self.onFailure(\\\"Sorry, the media is not playable.\\\")\\n        // highlight-end\\n        default:\\n        // highlight-start\\n            Log.info(\\\"Unknown status\\\")\\n        // highlight-end\\n        }\\n    }\\n\\n    private func setupObservers() {\\n\\n        let interval = CMTime(value: 1, timescale: 1)\\n        timeObserverToken = player.addPeriodicTimeObserver(forInterval: interval,\\n                                                           queue: .main) { [unowned self] time in\\n            self.updateProgress(with: time)\\n\\n        }\\n\\n        playerItemStatusObserver = player.observe(\\\\AVPlayer.currentItem?.status, options: [.new, .initial]) { [unowned self] _, _ in\\n            DispatchQueue.main.async {\\n                self.updatePlayerStatus()\\n            }\\n        }\\n    }\\n\\n    private func prepareAsset(_ asset: AVAsset) {\\n        let playableKeys =  [ \\\"playable\\\", \\\"hasProtectedContent\\\" ]\\n        asset.loadValuesAsynchronously(forKeys: playableKeys) { [weak self] in\\n            guard let self = self else { return }\\n\\n                        if self.validateValues(for: playableKeys, asset: asset) {\\n                    self.setupObservers()\\n                } \\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"If the item is read to play, we then call the play method on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"player\"), \" object to start playing, otherwise we send a failure message to the calling site.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer12\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    ...\\n\\n    func play(url: URL) {\\n\\n        let asset = AVAsset(url: url)\\n        let playerItem = AVPlayerItem(asset: asset)\\n        player.replaceCurrentItem(with: playerItem)\\n        prepareAsset(asset)\\n    }\\n\\n    // highlight-start\\n    func fastForward()  {\\n\\n        var time: TimeInterval = player.currentTime().seconds\\n        time += 15.0\\n        if player.currentItem?.currentTime() == player.currentItem?.duration {\\n            return\\n        }\\n\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    func fastBackward()  {\\n\\n        var time: TimeInterval = player.currentTime().seconds\\n        time -= 15.0 // Go back by 15 seconds\\n        if player.currentItem?.currentTime() == .zero {\\n            return\\n        }\\n\\n    }\\n    // highlight-end\\n\\n    private func validateValues(for keys: [String], asset: AVAsset) -> Bool {\\n        ...\\n    }\\n\\n    private func updateProgress(with time: CMTime)  {\\n        self.onTimeChange(time.seconds)\\n    }\\n\\n    private func updatePlayerStatus() {\\n        ...\\n    }\\n\\n    private func setupObservers() {\\n        ...\\n    }\\n\\n    private func prepareAsset(_ asset: AVAsset) {\\n        ...\\n    }\\n}\\n\")), mdx(\"p\", null, \"These 2 methods first check whether it's possible to fast backward or forward based on the current time.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer13\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    ...\\n\\n    func play(url: URL) {\\n\\n        let asset = AVAsset(url: url)\\n        let playerItem = AVPlayerItem(asset: asset)\\n        player.replaceCurrentItem(with: playerItem)\\n        prepareAsset(asset)\\n    }\\n\\n    // highlight-start\\n    func seek(to value: Double) {\\n        let time = CMTime(seconds: value, preferredTimescale: 1000)\\n        player.seek(to: time)\\n    }\\n    // highlight-end\\n\\n    func fastForward()  {\\n\\n        var time: TimeInterval = player.currentTime().seconds\\n        time += 5.0\\n        if player.currentItem?.currentTime() == player.currentItem?.duration {\\n            return\\n        }\\n\\n        // highlight-start\\n        seek(to: time)\\n        // highlight-start\\n    }\\n\\n    func fastBackward()  {\\n\\n        var time: TimeInterval = player.currentTime().seconds\\n        time -= 5.0 // Go back by 5 seconds\\n        if player.currentItem?.currentTime() == .zero {\\n            return\\n        }\\n\\n        // highlight-start\\n        seek(to: time)\\n        // highlight-start\\n    }\\n\\n    private func validateValues(for keys: [String], asset: AVAsset) -> Bool {\\n        ...\\n    }\\n\\n    private func updateProgress(with time: CMTime)  {\\n        self.onTimeChange(time.seconds)\\n    }\\n\\n    private func updatePlayerStatus() { ... }\\n\\n    private func setupObservers() { ... }\\n\\n    private func prepareAsset(_ asset: AVAsset) { ... }\\n}\\n\")), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"seek\"), \", we calculate the time based on the value provided, then call the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"seek\"), \" on the player object passing in the calculated time.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\n// $$liquidcoderID-Lesson26SCPlayer14\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    ...\\n\\n    // highlight-start\\n    func togglePlay()  {\\n        switch self.player.timeControlStatus {\\n        case .paused, .waitingToPlayAtSpecifiedRate:\\n            player.play()\\n            onTogglePlay(true)\\n        case .playing:\\n            player.pause()\\n            onTogglePlay(false)\\n        default:\\n            player.pause()\\n            onTogglePlay(false)\\n        }\\n    }\\n    // highlight-end\\n\\n    func seek(to value: Double) { ... }\\n\\n    func fastForward() { ... }\\n\\n    func fastBackward() { ... }\\n\\n    private func validateValues(for keys: [String], asset: AVAsset) -> Bool {\\n        ...\\n        }\\n\\n    private func updateProgress(with time: CMTime) {\\n        self.onTimeChange(time.seconds)\\n    }\\n\\n    private func updatePlayerStatus() {\\n        ...\\n    }\\n\\n    private func setupObservers() {\\n        ...\\n    }\\n\\n    private func prepareAsset(_ asset: AVAsset) {\\n        ...\\n    }\\n\\n}\\n\")), mdx(\"p\", null, \"The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"togglePlay\"), \" method will update the player status  between between \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"playing\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"paused\"), \".\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson26SCPlayer15\\n// $$liquidcoderFilename-SCPlayer\\n\\nimport Foundation\\nimport AVFoundation\\nimport MediaPlayer\\n\\nclass SCPlayer: NSObject {\\n\\n    ...\\n\\n    func seek(to value: Double) { ... }\\n\\n    func fastForward()  { ... }\\n\\n    func fastBackward()  { ... }\\n\\n    // highlight-start\\n    private func removePeriodicTimeObserver() {\\n        if let timeObserverToken = timeObserverToken {\\n            player.removeTimeObserver(timeObserverToken)\\n            self.timeObserverToken = nil\\n        }\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    deinit {\\n        removePeriodicTimeObserver()\\n    }\\n    // highlight-end\\n\\n}\\n\")), mdx(\"p\", null, \"Last, we remove the observers when this class is reinitialised to avoid memory related issues.\"), mdx(\"p\", null, \"And this bring lesson #26 to a close, this was just a player, in the next lesson we will build the manager that will be a bridge between the player with the view. See you then, happy coding!!!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#streamcast-player","title":"StreamCast Player"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-03-31T08:44:36.558Z","isNewLayout":true,"previews":null,"slug":"lesson-27-player-manager","title":"Lesson #27: Player Manager","updatedDate":"2021-03-31T00:00+01:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZC5RWXZOW2cayDgO4h6KJNLVHiOOjjdPBb7","type":"zip","id":"572b02fd-9fa4-5341-8ec0-5581eb38ba76","name":"Source code"}],"allChapters":null,"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson27Track1\\n// $$liquidcoderFilename-Track\\n\\nimport Foundation\\n\\nstruct Track: Equatable {\\n\\n    var title, imageUrl, podcastName, description, audioUrl, releaseDate: String\\n    var duration: Double\\n    var image: Data?\\n    var otherEpisodes: [EpisodeViewModel] = []\\n\\n    static var placeholder = Track(title: \\\"#1538 - Douglas Murray\\\", \\n                                        imageUrl: \\\"https://via.placeholder.com/150/2EB9F0/000000?Text=liquidcoder.com\\\", \\n                                        podcastName: \\\"The Joe Rogan Experience\\\",\\n                                        description: \\\"Writer, journalist, and political commentator Douglas Murray is the author\\\", \\n                                        audioUrl: \\\"\\\", releaseDate: \\\"wed, 30 Sep 2020\\\", duration: 2000)\\n\\n    init(title: String, imageUrl: String, podcastName: String, description: String, audioUrl: String, releaseDate: String, duration: Double) {\\n        self.title = title\\n        self.imageUrl = imageUrl\\n        self.podcastName = podcastName\\n        self.description = description\\n        self.audioUrl = audioUrl\\n        self.releaseDate = releaseDate\\n        self.duration = duration\\n    }\\n\\n    init(_ episode: EpisodeViewModel, otherEpisodes: [EpisodeViewModel]) {\\n        self.title = episode.title\\n        self.imageUrl = episode.podcast.imageUrl\\n        self.podcastName = episode.podcast.title\\n        self.image = episode.podcast.image\\n        self.description = episode.description\\n        self.audioUrl = episode.audioUrl\\n        self.releaseDate = episode.date\\n        self.duration = episode.audioLength\\n        self.otherEpisodes = otherEpisodes\\n    }\\n\\n    static func == (lhs: Track, rhs: Track) -> Bool {\\n        return lhs.title == rhs.title &&\\n            lhs.podcastName == rhs.podcastName\\n    }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson27PlayerManager1\\n// $$liquidcoderFilename-PlayerManager\\n\\nimport Foundation\\n\\nclass PlayerManager: ObservableObject {\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson27PlayerManager2\\n// $$liquidcoderFilename-PlayerManager\\n\\nimport Foundation\\n\\nclass PlayerManager: ObservableObject {\\n\\n    // highlight-start\\n    enum State {\\n        case idle\\n        case loading\\n        case playing\\n        case failed(_ message: String)\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    @Published var state: State = State.idle\\n    // highlight-end\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson27PlayerManager3\\n// $$liquidcoderFilename-PlayerManager\\n\\nimport Foundation\\n\\nclass PlayerManager: ObservableObject {\\n\\n    enum State {\\n        case idle\\n        case loading\\n        case playing\\n        case failed(_ message: String)\\n    }\\n\\n    @Published var state: State = State.idle\\n    // highlight-start\\n    @Published var currentTrack = Track.placeholder\\n    @Published var isPlaying: Bool = false\\n    @Published var currentTime: Double = 0\\n    @Published var duration: Double = 0\\n    // highlight-end\\n\\n    // highlight-start\\n    private var player = SCPlayer()\\n    // highlight-end\\n\\n    // highlight-start\\n    init() {\\n        setupPlayerObservers()\\n    }\\n    // highlight-start\\n\\n        // highlight-start\\n    private func setupPlayerObservers() {\\n\\n    }\\n        //  highlight-end\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson27PlayerManager4\\n// $$liquidcoderFilename-PlayerManager\\n\\nimport Foundation\\n\\nclass PlayerManager: ObservableObject {\\n\\n    enum State {\\n        case idle\\n        case loading\\n        case playing\\n        case failed(_ message: String)\\n    }\\n\\n    @Published var state: State = State.idle\\n    @Published var currentTrack = Track.placeholder\\n    @Published var isPlaying: Bool = false\\n    @Published var currentTime: Double = 0\\n    @Published var duration: Double = 0\\n\\n    private var player = SCPlayer()\\n\\n    init() {\\n        setupPlayerObservers()\\n    }\\n\\n    private func setupPlayerObservers() {\\n        // highlight-start\\n        player.onTimeChange = { [weak self] currentTime in\\n\\n        }\\n        // highlight-end\\n\\n        // highlight-start\\n        player.onTogglePlay = { [weak self] isPlaying in\\n\\n        }\\n        // highlight-end\\n\\n        // highlight-start\\n        player.onPlayerStart = { [weak self] duration in\\n\\n        }\\n        // highlight-end\\n\\n        // highlight-start\\n        player.onFailure = { [weak self] message in\\n\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson27PlayerManager5\\n// $$liquidcoderFilename-PlayerManager\\n\\nimport Foundation\\n\\nclass PlayerManager: ObservableObject {\\n\\n    enum State {\\n        case idle\\n        case loading\\n        case playing\\n        case failed(_ message: String)\\n    }\\n\\n    @Published var state: State = State.idle(Track.placeholder)\\n    @Published var currentTrack = Track.placeholder\\n    @Published var isPlaying: Bool = false\\n    @Published var currentTime: Double = 0\\n    @Published var duration: Double = 0\\n\\n    private var player = SCPlayer()\\n\\n    init() {\\n        setupPlayerObservers()\\n    }\\n\\n    private func setupPlayerObservers() {\\n        player.onTimeChange = { [weak self] currentTime in\\n            // highlight-start\\n            self?.currentTime = currentTime\\n            // highlight-end\\n        }\\n\\n        player.onTogglePlay = { [weak self] isPlaying in\\n            // highlight-start\\n            self?.isPlaying = isPlaying\\n            // highlight-end\\n        }\\n\\n        player.onPlayerStart = { [weak self] duration in\\n            // highlight-start\\n            guard let self = self else { return }\\n            DispatchQueue.main.async {\\n                self.duration = duration\\n                self.state = State.playing\\n            }\\n            // highlight-end\\n        }\\n\\n        player.onFailure = { [weak self] message in\\n            // highlight-start\\n            DispatchQueue.main.async {\\n                self?.state = .failed(message)\\n            }\\n            // highlight-end\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25DataExtension1\\n// $$liquidcoderFilename-Data+\\n\\nimport Foundation\\n\\nextension Data {\\n\\n    func write(to filename: String) -> URL {\\n        let dir = URL(fileURLWithPath: NSTemporaryDirectory(),\\n                      isDirectory: true)\\n\\n        let location = dir.appendingPathComponent(filename + \\\".mp3\\\")\\n\\n        if FileManager.default.fileExists(atPath: location.relativePath) {\\n            return location\\n        }\\n\\n        do {\\n            try self.write(to: location, options: WritingOptions.atomicWrite)\\n        } catch let error {\\n            Log.error(error)\\n        }\\n\\n        return location\\n    }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson27PlayerManager6\\n// $$liquidcoderFilename-PlayerManager\\n\\nimport Foundation\\n\\nclass PlayerManager: ObservableObject {\\n\\n    enum State {\\n        case idle\\n        case loading\\n        case playing\\n        case failed(_ message: String)\\n    }\\n\\n    @Published var state: State = State.idle\\n    @Published var currentTrack = Track.placeholder\\n    @Published var isPlaying: Bool = false\\n    @Published var currentTime: Double = 0\\n    @Published var duration: Double = 0\\n\\n    private var player = SCPlayer()\\n\\n    init() {\\n        setupPlayerObservers()\\n    }\\n\\n    private func setupPlayerObservers() {\\n        ...\\n    }\\n\\n    // highlight-start\\n    func play(_ track: Track) {\\n        guard let url = getAudioUrl(from: track) else {\\n            return\\n        }\\n\\n        // start playing...\\n\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    private func getAudioUrl(from track: Track) -> URL? {\\n        if let url = track.audio?.write(to: UUID().uuidString) {\\n            return url\\n        }\\n\\n        if let url = URL(string: track.audioUrl) {\\n            return url\\n        }\\n\\n        return nil\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson27PlayerManager7\\n// $$liquidcoderFilename-PlayerManager\\n\\nimport Foundation\\n\\nclass PlayerManager: ObservableObject {\\n\\n    enum State {\\n        case idle\\n        case loading\\n        case playing\\n        case failed(_ message: String)\\n    }\\n\\n    @Published var state: State = State.idle\\n    @Published var currentTrack = Track.placeholder\\n    @Published var isPlaying: Bool = false\\n    @Published var currentTime: Double = 0\\n    @Published var duration: Double = 0\\n\\n    private var player = SCPlayer()\\n\\n    init() {\\n        setupPlayerObservers()\\n    }\\n\\n    private func setupPlayerObservers() {\\n        ...\\n    }\\n\\n    func play(_ track: Track) {\\n        guard let url = getAudioUrl(from: track) else {\\n            return\\n        }\\n\\n        // highlight-start\\n        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) {[weak self] in\\n            self?.state = .loading\\n            self?.player.play(url: url)\\n            self?.currentTrack = track\\n\\n        }\\n        // highlight-end\\n\\n    }\\n\\n    private func getAudioUrl(from track: Track) -> URL? {\\n        if let url = track.audio?.write(to: UUID().uuidString) {\\n            return url\\n        }\\n\\n        if let url = URL(string: track.audioUrl) {\\n            return url\\n        }\\n\\n        return nil\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson27PlayerManager8\\n// $$liquidcoderFilename-PlayerManager\\n\\nimport Foundation\\n\\nclass PlayerManager: ObservableObject {\\n\\n    enum State {\\n        case idle\\n        case loading\\n        case playing\\n        case failed(_ message: String)\\n    }\\n\\n    @Published var state: State = State.idle\\n    @Published var currentTrack = Track.placeholder\\n    @Published var isPlaying: Bool = false\\n    @Published var currentTime: Double = 0\\n    @Published var duration: Double = 0\\n\\n    private var player = SCPlayer()\\n\\n    init() {\\n        setupPlayerObservers()\\n    }\\n\\n    private func setupPlayerObservers() { ... }\\n\\n    func play(_ track: Track) { ... }\\n\\n    // highlight-start\\n    func playPause()  {\\n        player.togglePlay()\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    func skip(percent: Double) {\\n        let time = duration * percent\\n        player.seek(to: time)\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    func fastForward()  {\\n        player.fastForward()\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    func fastBackward()  {\\n        player.fastBackward()\\n    }\\n    // highlight-end\\n\\n    private func getAudioUrl(from track: Track) -> URL? {\\n        if let url = track.audio?.write(to: UUID().uuidString) {\\n            return url\\n        }\\n\\n        if let url = URL(string: track.audioUrl) {\\n            return url\\n        }\\n\\n        return nil\\n    }\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson27Intro-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey ladies and gents, welcome back to lesson #27. In the previous lesson, we created the streamcast player, and in this one we will work on the player manager. Let's get started.\")), mdx(\"div\", {\n    id: \"Lesson27Track1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Track\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Models\")), \" folder, add a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Track.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS, macOS and watchOS-Extension targets\")), mdx(\"p\", null, \"This struct will be used to play an episode of type \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeViewModel\"), \" hence the overloaded \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"init\"), \" function that takes in an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeViewModel\"), \" .\")), mdx(\"div\", {\n    id: \"Lesson27PlayerManager0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Player Manager\"), mdx(\"p\", null, \"This class will be the one that views will interact with. It will play the role similar to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Store\"), \" or the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DownloadManager\"), \" classes, but will handle player related functionalities.\")), mdx(\"div\", {\n    id: \"Lesson27PlayerManager1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Player\")), \" folder, add a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PlayerManager.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS, macOS and watchOS-Extension targets\"))), mdx(\"div\", {\n    id: \"Lesson27PlayerManager2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We create yet another \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"enum\"), \" to keep track of the state of the currently playing item.\")), mdx(\"div\", {\n    id: \"Lesson27PlayerManager3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We add few more properties that  will all be observed by views which explains why they all have the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"@Published\"), \" property wrapper.\")), mdx(\"div\", {\n    id: \"Lesson27PlayerManager4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"setupPlayerObservers\"), \", we set all the closures from the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SCPlayer\"), \" to start listening to changes.\")), mdx(\"div\", {\n    id: \"Lesson27PlayerManager5-Section\",\n    className: \"post-section\"\n  }, mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onTimeChange\"), \" block, we update the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"currentTime\"), \" every time there's a change.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"isPlaying\"), \" will be toggled to \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"true\"), \" or \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"false\"), \".\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onPlayerStart\"), \" block, we update the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"state\"), \" to \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"playing\"), \" and set the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"duration\"), \" of the item.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"When we get a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"failure\"), \" event, we update the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"state\"), \" accordingly\"))), mdx(\"div\", {\n    id: \"Lesson25DataExtension1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Data Extension\"), mdx(\"p\", null, \"This extension helps us save downloaded episode audio data to a temporary file in order to play it using its location url because we can only create an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AVAsset\"), \" object using a url.\")), mdx(\"div\", {\n    id: \"Lesson27PlayerManager6-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"First, we get the url from the given track using that \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"getAudioUrl\"), \" created below.\"), mdx(\"hr\", null), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"getAudioUrl\"), \" method, we first try to write the data to a file using the extension method we've just created, if we can't, we return the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"track\"), \"'s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"audioUrl\"), \" instead.\")), mdx(\"div\", {\n    id: \"Lesson27PlayerManager7-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Having the url, we can now start playing the track on the main thread as the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"currentTrack\"), \" will also update the user interface.\")), mdx(\"div\", {\n    id: \"Lesson27PlayerManager8-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Last, but not least we implement the control functions that simply call \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SCPlayer\"), \" 's functions.\")), mdx(\"div\", {\n    id: \"Lesson27Outro-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And this brings this lesson to a close. In the next one, we will start working on the floating player component, see you there and happy coding!.\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#track","title":"Track"},{"url":"#player-manager","title":"Player Manager"},{"url":"#data-extension","title":"Data Extension"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey ladies and gents, welcome back to lesson #27. In the previous lesson, we created the streamcast player, and in this one we will work on the player manager. Let's get started.\"), mdx(\"h2\", null, \"Track\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Models\")), \" folder, add a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Track.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS, macOS and watchOS-Extension targets\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson27Track1\\n// $$liquidcoderFilename-Track\\n\\nimport Foundation\\n\\nstruct Track: Equatable {\\n\\n    var title, imageUrl, podcastName, description, audioUrl, releaseDate: String\\n    var duration: Double\\n    var image: Data?\\n    var audio: Data? = nil\\n    var nextEpisodes: [EpisodeViewModel] = []\\n\\n    static var placeholder = Track(title: \\\"#1538 - Douglas Murray Douglas\\\", imageUrl: \\\"https://via.placeholder.com/150/2EB9F0/000000?Text=liquidcoder.com\\\", podcastName: \\\"The Joe Rogan Experience\\\", description: \\\"Writer, journalist, and political\\\", audioUrl: \\\"\\\", releaseDate: \\\"wed, 30 Sep 2020\\\", duration: 2000)\\n\\n    init(title: String, imageUrl: String, podcastName: String, description: String, audioUrl: String, releaseDate: String, duration: Double) {\\n        self.title = title\\n        self.imageUrl = imageUrl\\n        self.podcastName = podcastName\\n        self.description = description\\n        self.audioUrl = audioUrl\\n        self.releaseDate = releaseDate\\n        self.duration = duration\\n    }\\n\\n    init(_ episode: EpisodeViewModel, nextEpisodes: [EpisodeViewModel]) {\\n        self.title = episode.title\\n        self.imageUrl = episode.podcast.imageUrl\\n        self.podcastName = episode.podcast.title\\n        self.image = episode.podcast.image\\n        self.description = episode.description\\n        self.audioUrl = episode.audioUrl\\n        self.releaseDate = episode.date\\n        self.duration = episode.audioLength\\n        self.audio = episode.audio\\n        self.nextEpisodes = nextEpisodes\\n    }\\n\\n    static func == (lhs: Track, rhs: Track) -> Bool {\\n        return lhs.title == rhs.title &&\\n            lhs.podcastName == rhs.podcastName}\\n\\n}\\n\")), mdx(\"p\", null, \"This struct will be used to play an episode of type \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeViewModel\"), \" hence the overloaded \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"init\"), \" function that takes in an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EpisodeViewModel\"), \" .\"), mdx(\"h2\", null, \"Player Manager\"), mdx(\"p\", null, \"This class will be the one that views will interact with. It will play the role similar to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Store\"), \" or the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DownloadManager\"), \" classes, but will handle player related functionalities.\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Player\")), \" folder, add a swift file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PlayerManager.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS, macOS and watchOS-Extension targets\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson27PlayerManager1\\n// $$liquidcoderFilename-PlayerManager\\n\\nimport Foundation\\n\\nclass PlayerManager: ObservableObject {\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson27PlayerManager2\\n// $$liquidcoderFilename-PlayerManager\\n\\nimport Foundation\\n\\nclass PlayerManager: ObservableObject {\\n\\n    // highlight-start\\n    enum State {\\n        case idle\\n        case loading\\n        case playing\\n        case failed(_ message: String)\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    @Published var state: State = State.idle(Track.placeholder)\\n    // highlight-end\\n\\n}\\n\")), mdx(\"p\", null, \"We create yet another \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"enum\"), \" to keep track of the state of the currently playing item.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson27PlayerManager3\\n// $$liquidcoderFilename-PlayerManager\\n\\nimport Foundation\\n\\nclass PlayerManager: ObservableObject {\\n\\n    enum State {\\n        case idle\\n        case loading\\n        case playing\\n        case failed(_ message: String)\\n    }\\n\\n    @Published var state: State = State.idle\\n    // highlight-start\\n    @Published var currentTrack = Track.placeholder\\n    @Published var isPlaying: Bool = false\\n    @Published var currentTime: Double = 0\\n    @Published var duration: Double = 0\\n    // highlight-end\\n\\n    // highlight-start\\n    private var player = SCPlayer()\\n    // highlight-end\\n\\n    // highlight-start\\n    init() {\\n        setupPlayerObservers()\\n    }\\n    // highlight-start\\n\\n        // highlight-start\\n        private func setupPlayerObservers() {\\n\\n        }\\n        // highlight-end\\n\\n}\\n\")), mdx(\"p\", null, \"We add few more properties that  will all be observed by views which explains why they all have the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"@Published\"), \" property wrapper.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson27PlayerManager4\\n// $$liquidcoderFilename-PlayerManager\\n\\nimport Foundation\\n\\nclass PlayerManager: ObservableObject {\\n\\n    enum State {\\n        case idle\\n        case loading\\n        case playing\\n        case failed(_ message: String)\\n    }\\n\\n    @Published var state: State = State.idle\\n    @Published var currentTrack = Track.placeholder\\n    @Published var isPlaying: Bool = false\\n    @Published var currentTime: Double = 0\\n    @Published var duration: Double = 0\\n\\n    private var player = SCPlayer()\\n\\n    init() {\\n        setupPlayerObservers()\\n    }\\n\\n    private func setupPlayerObservers() {\\n        // highlight-start\\n        player.onTimeChange = { [weak self] currentTime in\\n\\n        }\\n        // highlight-end\\n\\n        // highlight-start\\n        player.onTogglePlay = { [weak self] isPlaying in\\n\\n        }\\n        // highlight-end\\n\\n        // highlight-start\\n        player.onPlayerStart = { [weak self] duration in\\n\\n        }\\n        // highlight-end\\n\\n        // highlight-start\\n        player.onFailure = { [weak self] message in\\n\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"In \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"setupPlayerObservers\"), \", we set all the closures from the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SCPlayer\"), \" to start listening to changes.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson27PlayerManager5\\n// $$liquidcoderFilename-PlayerManager\\n\\nimport Foundation\\n\\nclass PlayerManager: ObservableObject {\\n\\n    enum State {\\n        case idle\\n        case loading\\n        case playing\\n        case failed(_ message: String)\\n    }\\n\\n    @Published var state: State = State.idle\\n    @Published var currentTrack = Track.placeholder\\n    @Published var isPlaying: Bool = false\\n    @Published var currentTime: Double = 0\\n    @Published var duration: Double = 0\\n\\n    private var player = SCPlayer()\\n\\n    init() {\\n        setupPlayerObservers()\\n    }\\n\\n    private func setupPlayerObservers() {\\n        player.onTimeChange = { [weak self] currentTime in\\n            // highlight-start\\n            self?.currentTime = currentTime\\n            // highlight-end\\n        }\\n\\n        player.onTogglePlay = { [weak self] isPlaying in\\n            // highlight-start\\n            self?.isPlaying = isPlaying\\n            // highlight-end\\n        }\\n\\n        player.onPlayerStart = { [weak self] duration in\\n            // highlight-start\\n            guard let self = self else { return }\\n            DispatchQueue.main.async {\\n                self.duration = duration\\n                self.state = State.playing\\n            }\\n            // highlight-end\\n        }\\n\\n        player.onFailure = { [weak self] message in\\n            // highlight-start\\n            DispatchQueue.main.async {\\n                self?.state = .failed(message)\\n            }\\n            // highlight-end\\n        }\\n    }\\n}\\n\")), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onTimeChange\"), \" block, we update the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"currentTime\"), \" every time there's a change.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"isPlaying\"), \" will be toggled to \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"true\"), \" or \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"false\"), \".\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"onPlayerStart\"), \" block, we update the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"state\"), \" to \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"playing\"), \" and set the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"duration\"), \" of the item.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"When we get a \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"failure\"), \" event, we update the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"state\"), \" accordingly\")), mdx(\"h2\", null, \"Data Extension\"), mdx(\"p\", null, \"This extension helps us save downloaded episode audio data to a temporary file in order to play it using its location url because we can only create an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AVAsset\"), \" object using a url.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson25DataExtension1\\n// $$liquidcoderFilename-Data+\\n\\nimport Foundation\\n\\nextension Data {\\n\\n    func write(to filename: String) -> URL {\\n        let dir = URL(fileURLWithPath: NSTemporaryDirectory(),\\n                      isDirectory: true)\\n\\n        let location = dir.appendingPathComponent(filename + \\\".mp3\\\")\\n\\n        if FileManager.default.fileExists(atPath: location.relativePath) {\\n            return location\\n        }\\n\\n        do {\\n            try self.write(to: location, options: WritingOptions.atomicWrite)\\n        } catch let error {\\n            Log.error(error)\\n        }\\n\\n        return location\\n    }\\n\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson27PlayerManager6\\n// $$liquidcoderFilename-PlayerManager\\n\\nimport Foundation\\n\\nclass PlayerManager: ObservableObject {\\n\\n    enum State {\\n        case idle\\n        case loading\\n        case playing\\n        case failed(_ message: String)\\n    }\\n\\n    @Published var state: State = State.idle\\n    @Published var currentTrack = Track.placeholder\\n    @Published var isPlaying: Bool = false\\n    @Published var currentTime: Double = 0\\n    @Published var duration: Double = 0\\n\\n    private var player = SCPlayer()\\n\\n    init() {\\n        setupPlayerObservers()\\n    }\\n\\n    private func setupPlayerObservers() {\\n        ...\\n    }\\n\\n    // highlight-start\\n    func play(_ track: Track) {\\n        guard let url = getAudioUrl(from: track) else {\\n            return\\n        }\\n\\n        // start playing...\\n\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    private func getAudioUrl(from track: Track) -> URL? {\\n        if let url = track.audio?.write(to: UUID().uuidString) {\\n            return url\\n        }\\n\\n        if let url = URL(string: track.audioUrl) {\\n            return url\\n        }\\n\\n        return nil\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"p\", null, \"First, we get the url from the given track using that \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"getAudioUrl\"), \" created below.\"), mdx(\"p\", null, \"In the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"getAudioUrl\"), \" method, we first try to write the data to a file using the extension method we've just created, if we can't, we return the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"track\"), \"'s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"audioUrl\"), \" instead.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson27PlayerManager7\\n// $$liquidcoderFilename-PlayerManager\\n\\nimport Foundation\\n\\nclass PlayerManager: ObservableObject {\\n\\n    enum State {\\n        case idle\\n        case loading\\n        case playing\\n        case failed(_ message: String)\\n    }\\n\\n    @Published var state: State = State.idle\\n    @Published var currentTrack = Track.placeholder\\n    @Published var isPlaying: Bool = false\\n    @Published var currentTime: Double = 0\\n    @Published var duration: Double = 0\\n\\n    private var player = SCPlayer()\\n\\n    init() {\\n        setupPlayerObservers()\\n    }\\n\\n    private func setupPlayerObservers() {\\n        ...\\n    }\\n\\n    func play(_ track: Track) {\\n        guard let url = getAudioUrl(from: track) else {\\n            return\\n        }\\n\\n        // highlight-start\\n        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) {[weak self] in\\n            self?.state = .loading\\n            self?.player.play(url: url)\\n            self?.currentTrack = track\\n\\n        }\\n        // highlight-end\\n\\n    }\\n\\n    private func getAudioUrl(from track: Track) -> URL? {\\n        if let url = track.audio?.write(to: UUID().uuidString) {\\n            return url\\n        }\\n\\n        if let url = URL(string: track.audioUrl) {\\n            return url\\n        }\\n\\n        return nil\\n    }\\n}\\n\")), mdx(\"p\", null, \"Having the url, we can now start playing the track on the main thread as the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"currentTrack\"), \" will also update the user interface.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson27PlayerManager8\\n// $$liquidcoderFilename-PlayerManager\\n\\nimport Foundation\\n\\nclass PlayerManager: ObservableObject {\\n\\n    enum State {\\n        case idle\\n        case loading\\n        case playing\\n        case failed(_ message: String)\\n    }\\n\\n    @Published var state: State = State.idle\\n    @Published var currentTrack = Track.placeholder\\n    @Published var isPlaying: Bool = false\\n    @Published var currentTime: Double = 0\\n    @Published var duration: Double = 0\\n\\n    private var player = SCPlayer()\\n\\n    init() {\\n        setupPlayerObservers()\\n    }\\n\\n    private func setupPlayerObservers() { ... }\\n\\n    func play(_ track: Track) { ... }\\n\\n    // highlight-start\\n    func playPause()  {\\n        player.togglePlay()\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    func skip(percent: Double) {\\n        let time = duration * percent\\n        player.seek(to: time)\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    func fastForward()  {\\n        player.fastForward()\\n    }\\n    // highlight-end\\n\\n    // highlight-start\\n    func fastBackward()  {\\n        player.fastBackward()\\n    }\\n    // highlight-end\\n\\n    private func getAudioUrl(from track: Track) -> URL? {\\n        if let url = track.audio?.write(to: UUID().uuidString) {\\n            return url\\n        }\\n\\n        if let url = URL(string: track.audioUrl) {\\n            return url\\n        }\\n\\n        return nil\\n    }\\n}\\n\")), mdx(\"p\", null, \"Last, but not least we implement the control functions that simply call \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SCPlayer\"), \" 's functions.\"), mdx(\"p\", null, \"And this brings this lesson to a close. In the next one, we will start working on the floating player component, see you there and happy coding!.\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#track","title":"Track"},{"url":"#player-manager","title":"Player Manager"},{"url":"#data-extension","title":"Data Extension"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-04-02T09:45:16.075Z","isNewLayout":true,"previews":{"previews":"{\n\t\"Lesson28TrackImageView1\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\t\"Lesson28TrackImageView1\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\t\"Lesson28TrackImageView3\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1616843217/StreamCast/Lesson%2028/ejmxmmbmnur1ajecjinz.png\"],\n\t\"Lesson28PlayerButton1\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\t\"Lesson28PlayerButton2\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1616843218/StreamCast/Lesson%2028/flfav9h2ddczcz7onil8.png\"],\n\t\"Lesson28FoldedPlayerComponent1\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\t\"Lesson28FoldedPlayerComponent2\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\t\"Lesson28FoldedPlayerComponent3\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1616843218/StreamCast/Lesson%2028/kgilhxoupi4sjh5jeo9w.png\"],\n\t\"Lesson28FoldedPlayerComponent4\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1616843218/StreamCast/Lesson%2028/kgilhxoupi4sjh5jeo9w.png\"]\n}"},"slug":"lesson-28-player-view-part-1","title":"Lesson #28: Player View Part 1","updatedDate":"2021-04-02T00:00+01:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZQOBWXZ9dBGRb0OfuRVcCVqJod8lXsBITyk","type":"zip","id":"82d3a3b8-4779-5517-81ab-3acfdac93331","name":"Source code"}],"allChapters":null,"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson28TrackImageView1\\n// $$liquidcoderFilename-TrackImageView\\n\\nimport SwiftUI\\n\\nstruct TrackImageView: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct TrackImageView_Previews: PreviewProvider {\\n    static var previews: some View {\\n         TrackImageView()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson28TrackImageView1\\n// $$liquidcoderFilename-TrackImageView\\n\\nimport SwiftUI\\n\\nstruct TrackImageView: View {\\n\\n    // highlight-start\\n    var track: Track\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct TrackImageView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            TrackImageView(track: Track.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            TrackImageView(track: Track.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson28TrackImageView3\\n// $$liquidcoderFilename-TrackImageView\\n\\nimport SwiftUI\\n\\nstruct TrackImageView: View {\\n\\n    var track: Track\\n\\n    // highlight-start\\n    var remoteImage: some View {\\n        RemoteImage(url: track.imageUrl, loading: {\\n            Rectangle()\\n                .foregroundColor(.border)\\n        }, failure: {\\n            ZStack {\\n                Rectangle()\\n                    .foregroundColor(.accentColor)\\n                Image(\\\"mic\\\")\\n                    .blendMode(.softLight)\\n\\n            }\\n        })\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        // highlight-start\\n        Group {\\n            if let image = track.image,\\n               let uiImage = UIImage(data: image) {\\n                Image(uiImage: uiImage)\\n                    .resizable()\\n            } else {\\n                remoteImage\\n            }\\n        }\\n        // highlight-end\\n    }\\n}\\n\\nstruct TrackImageView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            TrackImageView(track: Track.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            TrackImageView(track: Track.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson28PlayerButton1\\n// $$liquidcoderFilename-PlayerButton\\n\\nimport SwiftUI\\n\\nstruct PlayerButton: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct PlayerButton_Previews: PreviewProvider {\\n    static var previews: some View {\\n        PlayerButton()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson28PlayerButton2\\n// $$liquidcoderFilename-PlayerButton\\n\\nimport SwiftUI\\n\\nstruct PlayerButton: View {\\n\\n    // highlight-start\\n    var action = {}\\n    var icon = \\\"\\\"\\n    var isBig = false\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        let size: CGFloat = isBig ? 30 : 20\\n        // highlight-end\\n\\n        // highlight-start\\n        return Button(action: action, label: {\\n            Image(systemName: icon)\\n                .resizable()\\n                .font(.system(size: size, weight: .light, design: .rounded))\\n                .scaledToFit()\\n                .frame(width: size, height: size, alignment: .center)\\n                .foregroundColor(Color.gray)\\n        })\\n        // highlight-end\\n    }\\n}\\n\\nstruct PlayerButton_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            PlayerButton()\\n                .previewLayout(.sizeThatFits)\\n                .preferredColorScheme(.dark)\\n            PlayerButton()\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson28FoldedPlayerComponent1\\n// $$liquidcoderFilename-FoldedPlayerComponent\\n\\nimport SwiftUI\\n\\nstruct FoldedPlayerComponent: View {\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct FoldedPlayerComponent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        FoldedPlayerComponent()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson28FoldedPlayerComponent2\\n// $$liquidcoderFilename-FoldedPlayerComponent\\n\\nimport SwiftUI\\n\\nstruct FoldedPlayerComponent: View {\\n\\n    // highlight-start\\n    var track: Track\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct FoldedPlayerComponent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            FoldedPlayerComponent(track: Track.placeholder)\\n                .preferredColorScheme(.dark)\\n            FoldedPlayerComponent(track: Track.placeholder)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson28FoldedPlayerComponent3\\n// $$liquidcoderFilename-FoldedPlayerComponent\\n\\nimport SwiftUI\\n\\nstruct FoldedPlayerComponent: View {\\n\\n    var track: Track\\n\\n    var body: some View {\\n        // highlight-start\\n        HStack(spacing: 0) {\\n            TrackImageView(track: track)\\n                .frame(width: 70, height: 70)\\n                .cornerRadius(10)\\n                .zIndex(1.0)\\n\\n            VStack(alignment: .leading) {\\n                Text(track.title)\\n                    .font(.callout)\\n                    .lineLimit(2)\\n                Text(track.podcastName)\\n                    .font(.footnote)\\n                    .lineLimit(1)\\n                    .foregroundColor(.gray)\\n            }\\n\\n            PlayerButton(action: {}, icon: \\\"play.fill\\\", isBig: false)\\n                .frame(maxHeight: .infinity)\\n        }.padding(.horizontal)\\n        .padding(.vertical, 10)\\n        // highlight-end\\n    }\\n}\\n\\nstruct FoldedPlayerComponent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            FoldedPlayerComponent(track: Track.placeholder)\\n                .previewLayout(.sizeThatFits)\\n                .preferredColorScheme(.dark)\\n            FoldedPlayerComponent(track: Track.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson28FoldedPlayerComponent4\\n// $$liquidcoderFilename-FoldedPlayerComponent\\n\\nimport SwiftUI\\n\\nstruct FoldedPlayerComponent: View {\\n\\n    var track: Track\\n    // highlight-start\\n    @Binding var isExpanded: Bool\\n    @Binding var offsetY: CGFloat\\n    // highlight-end\\n\\n    var body: some View {\\n        HStack {\\n            TrackImageView(track: track)\\n                .frame(width: 70, height: 70)\\n                .cornerRadius(10)\\n                .zIndex(2.0)\\n\\n            VStack(alignment: .leading) {\\n                                            Text(track.title)\\n                    .font(.callout)\\n                    .lineLimit(2)\\n                Text(track.podcastName)\\n                    .font(.footnote)\\n                    .lineLimit(1)\\n                    .foregroundColor(.gray)\\n            }\\n\\n            PlayerButton(action: {}, icon: \\\"play.fill\\\", isBig: false)\\n                .frame(maxHeight: .infinity)\\n        }.padding(.horizontal)\\n        .padding(.vertical, 10)\\n         // highlight-start\\n                 .onTapGesture {\\n            self.isExpanded = true\\n            self.offsetY = 0\\n        }\\n        // highlight-end\\n    }\\n}\\n\\nstruct FoldedPlayerComponent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            // highlight-start\\n            FoldedPlayerComponent(track: Track.placeholder, isExpanded: .constant(false), offsetY: .constant(0))\\n                // highlight-end\\n                .previewLayout(.sizeThatFits)\\n                .preferredColorScheme(.dark)\\n            // highlight-start\\n            FoldedPlayerComponent(track: Track.placeholder, isExpanded: .constant(false), offsetY: .constant(0))\\n                // highlight-end\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson28Intro-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey ladies and gents, in the previous lesson, we finished the player manager, and in this one we will start creating the the player components. Let's jump right into it.\")), mdx(\"div\", {\n    id: \"Lesson28-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"TrackImageView\"), mdx(\"p\", null, \"This will be a wrapper around the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"remoteImage\"), \" created earlier in the course.\")), mdx(\"div\", {\n    id: \"Lesson28TrackImageView1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI \\u2192 Components\")), \", add another folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Player\")), \", and inside it create a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"TrackImageView.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS target only\"))), mdx(\"div\", {\n    id: \"Lesson28TrackImageView2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We will need the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"track\"), \" model that will come from the calling site.\")), mdx(\"div\", {\n    id: \"Lesson28TrackImageView3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We use the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RemoteImage\"), \" component to create the track \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"imageView\"), \" which is similar to what we did in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SCImageView\"), \" with some  minor differences.\")), mdx(\"div\", {\n    id: \"Lesson28PlayerButton1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"PlayerButton\"), mdx(\"p\", null, \"In the same folder, add another swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PlayerButton.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS target only\"))), mdx(\"div\", {\n    id: \"Lesson28PlayerButton2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We first declare a bunch of properties that we use down. \"), mdx(\"p\", null, \"Then we return the button containing the image icon styled correctly. We may get back to this view later, but this will do the job for now.\")), mdx(\"div\", {\n    id: \"Lesson28FoldedPlayerComponent0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"FoldedPlayerComponent\"), mdx(\"p\", null, \"This will display the player when folded as its name implies.\")), mdx(\"div\", {\n    id: \"Lesson28FoldedPlayerComponent1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the same folder, add another swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"FoldedPlayerComponent.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS target only\"))), mdx(\"div\", {\n    id: \"Lesson28FoldedPlayerComponent2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"First we bring in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"track\"), \" that's playing, then customise the preview.\")), mdx(\"div\", {\n    id: \"Lesson28FoldedPlayerComponent3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \", we use the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"TrackImageView\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PlayerButton\"), \" created a moment ago to display player details and controls.\")), mdx(\"div\", {\n    id: \"Lesson28FoldedPlayerComponent4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We need to update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isExpanded\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"offsetY\"), \" properties which will be \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" properties to be used for interaction. You will see how they work in the next lesson.\")), mdx(\"div\", {\n    id: \"Lesson28Outro-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And this brings this short lesson to a close. In the next one, we will continue working on the expanded component. See you then and happy coding!\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#trackimageview","title":"TrackImageView"},{"url":"#playerbutton","title":"PlayerButton"},{"url":"#foldedplayercomponent","title":"FoldedPlayerComponent"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey ladies and gents, in the previous lesson, we finished the player manager, and in this one we will start creating the the player components. Let's jump right into it.\"), mdx(\"h2\", null, \"TrackImageView\"), mdx(\"p\", null, \"This will be a wrapper around the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"remoteImage\"), \" created earlier in the course.\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI \\u2192 Components\")), \", add another folder named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Player\")), \", and inside it create a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"TrackImageView.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS target only\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson28TrackImageView2\\n// $$liquidcoderFilename-TrackImageView\\n\\nimport SwiftUI\\n\\nstruct TrackImageView: View {\\n\\n    // highlight-start\\n    var track: Track\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct TrackImageView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            TrackImageView(track: Track.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            TrackImageView(track: Track.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"We will need the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"track\"), \" model that will come from the calling site.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson28TrackImageView3\\n// $$liquidcoderFilename-TrackImageView\\n\\nimport SwiftUI\\n\\nstruct TrackImageView: View {\\n\\n    var track: Track\\n\\n    // highlight-start\\n    var remoteImage: some View {\\n        RemoteImage(url: track.imageUrl, loading: {\\n            Rectangle()\\n                .foregroundColor(.border)\\n        }, failure: {\\n            ZStack {\\n                Rectangle()\\n                    .foregroundColor(.accentColor)\\n                Image(\\\"mic\\\")\\n                    .blendMode(.softLight)\\n\\n            }\\n        })\\n    }\\n    // highlight-end\\n\\n    var body: some View {\\n        // highlight-start\\n        Group {\\n            if let image = track.image,\\n               let uiImage = UIImage(data: image) {\\n                Image(uiImage: uiImage)\\n                    .resizable()\\n            } else {\\n                remoteImage\\n            }\\n        }\\n        // highlight-end\\n    }\\n}\\n\\nstruct TrackImageView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            TrackImageView(track: Track.placeholder)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            TrackImageView(track: Track.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"We use the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RemoteImage\"), \" component to create the track \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"imageView\"), \" which is similar to what we did in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SCImageView\"), \" with some  minor differences.\"), mdx(\"h2\", null, \"PlayerButton\"), mdx(\"p\", null, \"In the same folder, add another swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PlayerButton.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS target only\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson28PlayerButton2\\n// $$liquidcoderFilename-PlayerButton\\n\\nimport SwiftUI\\n\\nstruct PlayerButton: View {\\n\\n    // highlight-start\\n    var action = {}\\n    var icon = \\\"\\\"\\n    var isBig = false\\n    // highlight-end\\n\\n    var body: some View {\\n\\n        // highlight-start\\n        let size: CGFloat = isBig ? 30 : 20\\n        // highlight-end\\n\\n        // highlight-start\\n        return Button(action: action, label: {\\n            Image(systemName: icon)\\n                .resizable()\\n                .font(.system(size: size, weight: .light, design: .rounded))\\n                .scaledToFit()\\n                .frame(width: size, height: size, alignment: .center)\\n                .foregroundColor(Color.gray)\\n        })\\n        // highlight-end\\n    }\\n}\\n\\nstruct PlayerButton_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            PlayerButton()\\n                .previewLayout(.sizeThatFits)\\n                .preferredColorScheme(.dark)\\n            PlayerButton()\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"We first declare a bunch of properties that we use down. \"), mdx(\"p\", null, \"Then we return the button containing the image icon styled correctly. We may get back to this view later, but this will do the job for now.\"), mdx(\"h2\", null, \"FoldedPlayerComponent\"), mdx(\"p\", null, \"This will display the player when folded as its name implies.\"), mdx(\"p\", null, \"In the same folder, add another swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"FoldedPlayerComponent.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS target only\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson28FoldedPlayerComponent2\\n// $$liquidcoderFilename-FoldedPlayerComponent\\n\\nimport SwiftUI\\n\\nstruct FoldedPlayerComponent: View {\\n\\n    // highlight-start\\n    var track: Track\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct FoldedPlayerComponent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            FoldedPlayerComponent(track: Track.placeholder)\\n                .preferredColorScheme(.dark)\\n            FoldedPlayerComponent(track: Track.placeholder)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"First we bring in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"track\"), \" that's playing, then customise the preview.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson28FoldedPlayerComponent3\\n// $$liquidcoderFilename-FoldedPlayerComponent\\n\\nimport SwiftUI\\n\\nstruct FoldedPlayerComponent: View {\\n\\n    var track: Track\\n\\n    var body: some View {\\n        // highlight-start\\n        HStack(spacing: 0) {\\n            TrackImageView(track: track)\\n                .frame(width: 70, height: 70)\\n                .cornerRadius(10)\\n                .zIndex(1.0)\\n\\n            VStack(alignment: .leading) {\\n                Text(track.title)\\n                    .font(.callout)\\n                    .lineLimit(2)\\n                Text(track.podcastName)\\n                    .font(.footnote)\\n                    .lineLimit(1)\\n                    .foregroundColor(.gray)\\n            }\\n\\n            PlayerButton(action: {}, icon: \\\"play.fill\\\", isBig: false)\\n                .frame(maxHeight: .infinity)\\n        }.padding(.horizontal)\\n        .padding(.vertical, 10)\\n        // highlight-end\\n    }\\n}\\n\\nstruct FoldedPlayerComponent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            FoldedPlayerComponent(track: Track.placeholder)\\n                .previewLayout(.sizeThatFits)\\n                .preferredColorScheme(.dark)\\n            FoldedPlayerComponent(track: Track.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"In \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \", we use the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"TrackImageView\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PlayerButton\"), \" created a moment ago to display player details and controls.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson28FoldedPlayerComponent4\\n// $$liquidcoderFilename-FoldedPlayerComponent\\n\\nimport SwiftUI\\n\\nstruct FoldedPlayerComponent: View {\\n\\n    var track: Track\\n    // highlight-start\\n    @Binding var isExpanded: Bool\\n    @Binding var offsetY: CGFloat\\n    // highlight-end\\n\\n    var body: some View {\\n        HStack {\\n            TrackImageView(track: track)\\n                .frame(width: 70, height: 70)\\n                .cornerRadius(10)\\n                .zIndex(2.0)\\n\\n            VStack(alignment: .leading) {\\n                                            Text(track.title)\\n                    .font(.callout)\\n                    .lineLimit(2)\\n                Text(track.podcastName)\\n                    .font(.footnote)\\n                    .lineLimit(1)\\n                    .foregroundColor(.gray)\\n            }\\n\\n            PlayerButton(action: {}, icon: \\\"play.fill\\\", isBig: false)\\n                .frame(maxHeight: .infinity)\\n        }.padding(.horizontal)\\n        .padding(.vertical, 10)\\n         // highlight-start\\n                 .onTapGesture {\\n            self.isExpanded = true\\n            self.offsetY = 0\\n        }\\n        // highlight-end\\n    }\\n}\\n\\nstruct FoldedPlayerComponent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            // highlight-start\\n            FoldedPlayerComponent(track: Track.placeholder, isExpanded: .constant(false), offsetY: .constant(0))\\n                // highlight-end\\n                .previewLayout(.sizeThatFits)\\n                .preferredColorScheme(.dark)\\n            // highlight-start\\n            FoldedPlayerComponent(track: Track.placeholder, isExpanded: .constant(false), offsetY: .constant(0))\\n                // highlight-end\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"We need to update the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isExpanded\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"offsetY\"), \" properties which will be \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" properties to be used for interaction. You will see how they work in the next lesson.\"), mdx(\"p\", null, \"And this brings this short lesson to a close. In the next one, we will continue working on the expanded component. See you then and happy coding!\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#trackimageview","title":"TrackImageView"},{"url":"#playerbutton","title":"PlayerButton"},{"url":"#foldedplayercomponent","title":"FoldedPlayerComponent"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-04-05T09:09:09.811Z","isNewLayout":true,"previews":{"previews":"{\n\"Lesson29Scrollbar1\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\"Lesson29Scrollbar2\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\"Lesson29Scrollbar3\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1617606987/StreamCast/Lesson%2029/j4txy0g5pidlorqhdakx.png\"],\n\"Lesson29Scrollbar4\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1617606987/StreamCast/Lesson%2029/j4txy0g5pidlorqhdakx.png\"],\n\"Lesson29Scrollbar5\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1617606922/StreamCast/Lesson%2029/h1wyoef2umiufcijy12c.gif\"],\n\"Lesson29Scrollbar6\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1617606922/StreamCast/Lesson%2029/h1wyoef2umiufcijy12c.gif\"],\n\"Lesson29PlayerProgressView1\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\"Lesson29PlayerProgressView2\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1617606921/StreamCast/Lesson%2029/odhrdmoujbpyqd1ioplk.gif\"],\n\"Lesson29ExpandedPlayerComponent1\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\"Lesson29ExpandedPlayerComponent2\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\"Lesson29ExpandedPlayerComponent3\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1617606987/StreamCast/Lesson%2029/y5u1zqbe4xaz1msstozg.png\"],\n\"Lesson29ExpandedPlayerComponent4\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1617606987/StreamCast/Lesson%2029/qdlfquoed0wnrlxdy2qk.png\"],\n\"Lesson29ExpandedPlayerComponent5\":[\"https://res.cloudinary.com/liquidcoder/image/upload/v1617606987/StreamCast/Lesson%2029/qdlfquoed0wnrlxdy2qk.png\"]\n\n}"},"slug":"lesson-29-player-view-part-2","title":"Lesson #29: Player View Part 2","updatedDate":"2021-04-05T00:00+01:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZ1hKWXZO87OWFKxWWXQ62izv3Tin0rXs470","type":"zip","id":"90adae20-c6b4-5d07-9709-c4f1a282973a","name":"Source code"}],"allChapters":null,"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson29Scrollbar1\\n// $$liquidcoderFilename-Scrollbar\\n\\nimport SwiftUI\\n\\nstruct Scrollbar: View {\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct Scrollbar_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Scrollbar()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson29Scrollbar2\\n// $$liquidcoderFilename-Scrollbar\\n\\nimport SwiftUI\\n\\nstruct Scrollbar: View {\\n\\n    // highlight-start\\n    @Binding var currentTime: Double\\n    var totalTime: Double\\n    var onFinish: ((Double) -> Void) = {_ in }\\n    // highlight-end\\n\\n    // highlight-start\\n    @State private var offsetX: CGFloat = 0.0\\n    @State private var percent: CGFloat = 0.0\\n    @State private var timeIndicatorOffsetX: CGFloat = 0\\n    @State private var isChanging: Bool = false\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct Scrollbar_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            Scrollbar(currentTime: .constant(2000), totalTime: 3000)\\n                .preferredColorScheme(.dark)\\n                                .previewLayout(.fixed(width: 350.0, height: 50))\\n            Scrollbar(currentTime: .constant(2000), totalTime: 3000)\\n                                .previewLayout(.fixed(width: 350.0, height: 50))\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson29Scrollbar3\\n// $$liquidcoderFilename-Scrollbar\\n\\nimport SwiftUI\\n\\nstruct Scrollbar: View {\\n\\n    @Binding var currentTime: Double\\n    var totalTime: Double\\n    var onFinish: ((Double) -> Void) = {_ in }\\n\\n    @State private var offsetX: CGFloat = 0.0\\n    @State private var percent: CGFloat = 0.0\\n    @State private var timeIndicatorOffsetX: CGFloat = 0\\n    @State private var isChanging: Bool = false\\n\\n    var body: some View {\\n        // highlight-start\\n            GeometryReader { geometry in\\n            ZStack(alignment: .leading) {\\n                RoundedRectangle(cornerSize: CGSize(width: 5, height: 5))\\n                    .foregroundColor(Color.accentColor.opacity(0.3))\\n                    .frame(width: nil, height: geometry.size.height, alignment: .center)\\n\\n                RoundedRectangle(cornerSize: CGSize(width: 3, height: 3))\\n                    .foregroundColor(Color.accentColor)\\n                    .frame(width: offsetX, height: geometry.size.height, alignment: .center)\\n            }\\n        }\\n        // highlight-end\\n    }\\n}\\n\\nstruct Scrollbar_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            Scrollbar(currentTime: .constant(2000), totalTime: 3000)\\n                .previewLayout(.fixed(width: 350.0, height: 50))\\n                .preferredColorScheme(.dark)\\n            Scrollbar(currentTime: .constant(2000), totalTime: 3000)\\n                .previewLayout(.fixed(width: 350.0, height: 50))\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson29Scrollbar4\\n// $$liquidcoderFilename-Scrollbar\\n\\nimport SwiftUI\\n\\nstruct Scrollbar: View {\\n\\n    @Binding var currentTime: Double\\n    var totalTime: Double\\n    var onFinish: ((Double) -> Void) = {_ in }\\n\\n    @State private var offsetX: CGFloat = 0.0\\n    @State private var percent: CGFloat = 0.0\\n    @State private var timeIndicatorOffsetX: CGFloat = 0\\n    @State private var isChanging: Bool = false\\n\\n    var body: some View {\\n        GeometryReader { geometry in\\n            ZStack(alignment: .leading) {\\n                RoundedRectangle(cornerSize: CGSize(width: 5, height: 5))\\n                    .foregroundColor(Color.accentColor.opacity(0.3))\\n                    .frame(width: nil, height: geometry.size.height, alignment: .center)\\n\\n                RoundedRectangle(cornerSize: CGSize(width: 3, height: 3))\\n                    .foregroundColor(Color.accentColor)\\n                    .frame(width: offsetX, height: geometry.size.height, alignment: .leading)\\n                // highlight-start\\n            }.gesture(\\n                DragGesture()\\n                    .onChanged({ drag in\\n                        self.offsetX = min(max(drag.location.x, 0), geometry.size.width)\\n                        self.percent = self.offsetX / (geometry.size.width)\\n                    }).onEnded({ drag in\\n                        self.offsetX = min(max(drag.location.x, 0), geometry.size.width)\\n                    })\\n            )\\n            // highlight-end\\n        }\\n    }\\n}\\n\\nstruct Scrollbar_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            Scrollbar(currentTime: .constant(2000), totalTime: 3000)\\n                .previewLayout(.fixed(width: 350.0, height: 50))\\n                .preferredColorScheme(.dark)\\n            Scrollbar(currentTime: .constant(2000), totalTime: 3000)\\n                .previewLayout(.fixed(width: 350.0, height: 50))\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson29DoubleExt1\\n// $$liquidcoderFilename-Double+\\n\\nimport Foundation\\n\\nextension Double {\\n  func asString(style: DateComponentsFormatter.UnitsStyle = .positional) -> String {\\n    let formatter = DateComponentsFormatter()\\n    formatter.allowedUnits = self >= 3600 ? [.hour, .minute, .second] : [.minute, .second]\\n    formatter.unitsStyle = style\\n    formatter.zeroFormattingBehavior = .pad\\n    guard let formattedString = formatter.string(from: self) else { return \\\"\\\" }\\n    return formattedString\\n  }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson29Scrollbar5\\n// $$liquidcoderFilename-Scrollbar\\n\\nimport SwiftUI\\n\\nstruct Scrollbar: View {\\n\\n    @Binding var currentTime: Double\\n    var totalTime: Double\\n    var onFinish: ((Double) -> Void) = {_ in }\\n\\n    @State private var offsetX: CGFloat = 0.0\\n    @State private var percent: CGFloat = 0.0\\n    @State private var timeIndicatorOffsetX: CGFloat = 0\\n    @State private var isChanging: Bool = false\\n\\n    var body: some View {\\n        GeometryReader { geometry in\\n            ZStack(alignment: .leading) {\\n                RoundedRectangle(cornerSize: CGSize(width: 5, height: 5))\\n                    .foregroundColor(Color.accentColor.opacity(0.3))\\n                    .frame(width: nil, height: geometry.size.height, alignment: .center)\\n\\n                RoundedRectangle(cornerSize: CGSize(width: 3, height: 3))\\n                    .foregroundColor(Color.accentColor)\\n                    .frame(width: offsetX, height: geometry.size.height, alignment: .leading)\\n\\n                // highlight-start\\n                if isChanging {\\n                    Text((Double(percent) * totalTime)\\n                            .asString(style: .positional))\\n                        .font(.caption)\\n                        .offset(x:  timeIndicatorOffsetX, y: -geometry.size.height)\\n                }\\n                // highlight-end\\n            }.gesture(\\n                DragGesture()\\n                    .onChanged({ drag in\\n                        self.offsetX = min(max(drag.location.x, 0), geometry.size.width)\\n                        self.percent = self.offsetX / (geometry.size.width)\\n                        // highlight-start\\n                        updateIndicatorOffset()\\n                        if !self.isChanging { self.isChanging.toggle() }\\n                        // highlight-end\\n                    }).onEnded({ drag in\\n                        self.offsetX = min(max(drag.location.x, 0), geometry.size.width)\\n                        // highlight-start\\n                        self.isChanging = false\\n                        // highlight-end\\n                    })\\n            )\\n        }\\n    }\\n\\n    // highlight-start\\n    private func updateIndicatorOffset() {\\n        if percent > 0.8 {\\n            timeIndicatorOffsetX = offsetX - 40\\n        } else if percent < 0.2 {\\n            timeIndicatorOffsetX = offsetX\\n        } else {\\n            timeIndicatorOffsetX = offsetX - 20\\n        }\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson29Scrollbar6\\n// $$liquidcoderFilename-Scrollbar\\n\\nimport SwiftUI\\n\\nstruct Scrollbar: View {\\n\\n    @Binding var currentTime: Double\\n    var totalTime: Double\\n    var onFinish: ((Double) -> Void) = {_ in }\\n\\n    @State private var offsetX: CGFloat = 0.0\\n    @State private var percent: CGFloat = 0.0\\n    @State private var timeIndicatorOffsetX: CGFloat = 0\\n    @State private var isChanging: Bool = false\\n\\n    var body: some View {\\n        GeometryReader { geometry in\\n            ZStack(alignment: .leading) {\\n                RoundedRectangle(cornerSize: CGSize(width: 5, height: 5))\\n                    .foregroundColor(Color.accentColor.opacity(0.3))\\n                    .frame(width: nil, height: geometry.size.height, alignment: .center)\\n\\n                RoundedRectangle(cornerSize: CGSize(width: 3, height: 3))\\n                    .foregroundColor(Color.accentColor)\\n                    .frame(width: offsetX, height: geometry.size.height, alignment: .leading)\\n\\n                if isChanging {\\n                    Text((Double(percent) * totalTime)\\n                            .asString(style: .positional))\\n                        .font(.caption)\\n                        .offset(x: timeIndicatorOffsetX, y: -geometry.size.height)\\n                }\\n                // highlight-start\\n            }.frame(height: geometry.size.height + 10, alignment: .center)\\n            // highlight-end\\n            .gesture(\\n                DragGesture()\\n                    .onChanged({ drag in\\n                        self.offsetX = min(max(drag.location.x, 0), geometry.size.width)\\n                        self.percent = self.offsetX / (geometry.size.width)\\n                        updateIndicatorOffset()\\n                        if !self.isChanging { self.isChanging.toggle() }\\n                    }).onEnded({ drag in\\n                        self.offsetX = min(max(drag.location.x, 0), geometry.size.width)\\n                        // highlight-start\\n                        onFinish(Double(offsetX / (geometry.size.width)))\\n                        // highlight-end\\n                        self.isChanging = false\\n                    })\\n                // highlight-start\\n            )\\n            .onChange(of: currentTime, perform: { value in\\n                self.percent = CGFloat(value / totalTime)\\n                self.offsetX = percent * geometry.size.width\\n            })\\n            // highlight-end\\n        }\\n    }\\n\\n    private func updateIndicatorOffset() {\\n        if percent > 0.8 {\\n            timeIndicatorOffsetX = offsetX - 40\\n        } else if percent < 0.2 {\\n            timeIndicatorOffsetX = offsetX\\n        } else {\\n            timeIndicatorOffsetX = offsetX - 20\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson29PlayerProgressView1\\n// $$liquidcoderFilename-PlayerProgressView\\n\\nimport SwiftUI\\n\\nstruct PlayerProgressView: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct PlayerProgressView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        PlayerProgressView()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson29PlayerProgressView2\\n// $$liquidcoderFilename-PlayerProgressView\\n\\nimport SwiftUI\\n\\nstruct PlayerProgressView: View {\\n\\n    // highlight-start\\n    var currentTime: Double\\n    var totalTime: Double\\n    var onFinishSeeking: ((Double) -> Void) = { _ in }\\n    // highlight-end\\n\\n    var body: some View {\\n        // highlight-start\\n        VStack {\\n             Scrollbar(currentTime: .constant(currentTime),\\n                      totalTime: totalTime, onFinish:  { value in\\n                onFinishSeeking(totalTime * value)\\n                      }).frame(height: 15, alignment: .center)\\n\\n            HStack{\\n                Text(\\\"\\\\(currentTime.asString(style: .positional))\\\")\\n                    .font(.caption)\\n                Spacer()\\n                Text(\\\"\\\\(totalTime.asString(style: .positional))\\\")\\n                    .font(.caption)\\n            }\\n\\n        }.frame(height: 40, alignment: .center)\\n        // highlight-end\\n    }\\n}\\n\\nstruct PlayerProgressView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            PlayerProgressView(currentTime: 2000, totalTime: 3000)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PlayerProgressView(currentTime: 2000, totalTime: 3000)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson29ExpandedPlayerComponent1\\n// $$liquidcoderFilename-ExpandedPlayerComponent\\n\\nimport SwiftUI\\n\\nstruct ExpandedPlayerComponent: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct ExpandedPlayerComponent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        ExpandedPlayerComponent()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson29ExpandedPlayerComponent2\\n// $$liquidcoderFilename-ExpandedPlayerComponent\\n\\nimport SwiftUI\\n\\nstruct ExpandedPlayerComponent: View {\\n\\n    // highlight-start\\n    var track: Track\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct ExpandedPlayerComponent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            ExpandedPlayerComponent(track: Track.placeholder)\\n                .previewLayout(.sizeThatFits)\\n            ExpandedPlayerComponent(track: Track.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson29ExpandedPlayerComponent3\\n// $$liquidcoderFilename-ExpandedPlayerComponent\\n\\nimport SwiftUI\\n\\nstruct ExpandedPlayerComponent: View {\\n\\n    var track: Track\\n\\n    var body: some View {\\n        // highlight-start\\n        VStack(spacing: 40) {\\n            TrackImageView(track: track)\\n                .frame(width: 200, height: 200, alignment: .center)\\n                .cornerRadius(15)\\n\\n            VStack {\\n                Text(track.title)\\n                    .font(.callout)\\n                    .lineLimit(2)\\n                    .multilineTextAlignment(.center)\\n                Text(track.podcastName)\\n                    .lineLimit(2)\\n                    .foregroundColor(.gray)\\n            }\\n\\n            PlayerProgressView(currentTime: 200, totalTime: 300)\\n                                    .frame(maxWidth: 300, alignment: .center)            \\n        }.padding(40)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson29ExpandedPlayerComponent4\\n// $$liquidcoderFilename-ExpandedPlayerComponent\\n\\nimport SwiftUI\\n\\nstruct ExpandedPlayerComponent: View {\\n\\n    var track: Track\\n\\n    var body: some View {\\n        VStack(spacing: 40) {\\n            TrackImageView(track: track)\\n                .frame(width: 200, height: 200, alignment: .center)\\n                .cornerRadius(15)\\n\\n            VStack {\\n                Text(track.title)\\n                    .font(.callout)\\n                    .lineLimit(2)\\n                    .multilineTextAlignment(.center)\\n                Text(track.podcastName)\\n                    .lineLimit(2)\\n                    .foregroundColor(.gray)\\n            }\\n\\n            PlayerProgressView(currentTime: 200, totalTime: 300)\\n                          .frame(maxWidth: 300, alignment: .center)\\n        // highlight-start\\n            createButtonsRow()\\n        // highlight-end\\n        }.padding(40)\\n    }\\n\\n        // highlight-start\\n    private func createButtonsRow() -> some View {\\n        HStack(spacing: 50) {\\n            PlayerButton(action: {\\n\\n            }, icon: \\\"gobackward.15\\\", isBig: true)\\n            .cornerRadius(4)\\n\\n            PlayerButton(action: {\\n\\n            }, icon: \\\"play.fill\\\", isBig: true)\\n\\n            PlayerButton(action: {\\n\\n            }, icon:  \\\"goforward.15\\\", isBig: true)\\n            .cornerRadius(4)\\n\\n        }\\n    }\\n        // highlight-end\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson29ExpandedPlayerComponent5\\n// $$liquidcoderFilename-ExpandedPlayerComponent\\n\\nimport SwiftUI\\n\\nstruct ExpandedPlayerComponent: View {\\n\\n    var track: Track\\n    // highlight-start\\n    @Binding var isExpanded: Bool\\n    @Binding var offsetY: CGFloat\\n    private let threshold: CGFloat = 100\\n    // highlight-end\\n\\n    var body: some View {\\n        VStack(spacing: 40) {\\n            TrackImageView(track: track)\\n                .frame(width: 200, height: 200, alignment: .center)\\n                .cornerRadius(15)\\n\\n            VStack {\\n                Text(track.title)\\n                    .font(.callout)\\n                    .lineLimit(2)\\n                    .multilineTextAlignment(.center)\\n                Text(track.podcastName)\\n                    .lineLimit(2)\\n                    .foregroundColor(.gray)\\n            }\\n\\n            PlayerProgressView(currentTime: 200, totalTime: 300)\\n                                    .frame(maxWidth: 300, alignment: .center)\\n            createButtonsRow()\\n        }.padding(40)\\n        // highlight-start\\n        .offset(x: 0, y: self.offsetY)\\n        .gesture(\\n            DragGesture().onChanged({ value in\\n                    self.offsetY = value.translation.height > 0 ? value.translation.height / 2 : 0\\n            }).onEnded({ value in\\n                if value.translation.height > self.threshold {\\n                    isExpanded = false\\n                }\\n            })\\n        )\\n        // highlight-end\\n    }\\n\\n    private func createButtonsRow() -> some View {\\n        ...\\n    }\\n}\\n\\nstruct ExpandedPlayerComponent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            // highlight-start\\n            ExpandedPlayerComponent(track: Track.placeholder, isExpanded: .constant(false), offsetY: .constant(0))\\n                // highlight-end\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            // highlight-start\\n            ExpandedPlayerComponent(track: Track.placeholder, isExpanded: .constant(false), offsetY: .constant(0))\\n                // highlight-end\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson29Intro-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hello ladies and gents, welcome back to lesson # 29.  In the previous one, we started working on the player view, and in this one, we will continue from where we left off. Let's jump right into it.\")), mdx(\"div\", {\n    id: \"Lesson29Scrollbar1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Player Scrollbar\"), mdx(\"p\", null, \"This scrollbar will only be visible on the the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ExpandedPlayerComponent\"), \" which will be created shortly.\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI \\u2192 Components \\u2192 Player\")), \" folder, add a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Scrollbar.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Only check the iOS target\"))), mdx(\"div\", {\n    id: \"Lesson29Scrollbar2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We first declare properties that we will receive from the calling site as well as the local state properties.\")), mdx(\"div\", {\n    id: \"Lesson29Scrollbar3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We then create the interactive bar that will be used to update the player's current time as well as change the current time using a similar trick we used when creating the circular progress view stacking 2 shape on the z-axis.\")), mdx(\"div\", {\n    id: \"Lesson29Scrollbar4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We attach a gesture modifier to make the bar interactive. \"), mdx(\"hr\", null), mdx(\"p\", null, \"We set the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"offsetX\"), \" to move between the width of the entire view and zero that's what the min and max functions do. \")), mdx(\"div\", {\n    id: \"Lesson29DoubleExt1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Double Extension\"), mdx(\"p\", null, \"Let's create this extension on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Double\"), \" type  in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Extensions\")), \" folder before continuing with our scrollbar.\"), mdx(\"hr\", null), mdx(\"p\", null, \"Here we are formatting a double value to time format.\")), mdx(\"div\", {\n    id: \"Lesson29Scrollbar5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We want to show a small text displaying the current time when seeking to a particular position, and we will show that \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" only when \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isChanging\"), \" is \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"true\"), \".\"), mdx(\"p\", null, \"We then update \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isChanging\"), \" whenever the user starts seeking as well as when he finishes. \")), mdx(\"div\", {\n    id: \"Lesson29Scrollbar6-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Last for this component, we notify the calling view that we've finished seeking by calling the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onFinish\"), \" closure.\"), mdx(\"hr\", null), mdx(\"p\", null, \"We also update the percent and the offset when \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"currentTime\"), \" changes to update the player progress.\")), mdx(\"div\", {\n    id: \"Lesson29PlayerProgressView0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Player ProgressView\"), mdx(\"p\", null, \"This view will utilise the scrollbar to create a component that will be used in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ExpandedPlayerComponent\"))), mdx(\"div\", {\n    id: \"Lesson29PlayerProgressView1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"In the same folder, add a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PlayerProgressView.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Only check the iOS target\"))), mdx(\"div\", {\n    id: \"Lesson29PlayerProgressView2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"This view is simpler than it looks. We just stack the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Scrollbar\"), \" with the 2 \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" views vertically.\")), mdx(\"div\", {\n    id: \"Lesson29ExpandedPlayerComponent1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"ExpandedPlayerComponent\"), mdx(\"p\", null, \"In the same folder, add a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"ExpandedPlayerComponent.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Only check the iOS target\"))), mdx(\"div\", {\n    id: \"Lesson29ExpandedPlayerComponent2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Just like folded one, we also need a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"track\"), \" here.\")), mdx(\"div\", {\n    id: \"Lesson29ExpandedPlayerComponent3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Here, we stack the image, titles and the progress view vertically\")), mdx(\"div\", {\n    id: \"Lesson29ExpandedPlayerComponent4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Next, we create the buttons row which consists of a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HStack\"), \" containing 3 instances of \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PlayerButton\"), \" that will control the player.\")), mdx(\"div\", {\n    id: \"Lesson29ExpandedPlayerComponent5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Just like the folded one, the expanded component also need to control the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isExpanded\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"offsetY\"), \" properties.\"), mdx(\"hr\", null), mdx(\"p\", null, \"We then add an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"offset\"), \" and a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DragGesture\"), \" to the entire component to make it dismissible. We divide the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"value.translation.height\"), \" by 2 to create a little resistance when dismissing.\")), mdx(\"div\", {\n    id: \"Lesson29Outro-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And this brings this lesson to a close. In the next one, we will create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PlayerView\"), \" which will put these 2 components together to create an amazing interactive floating player component. See you then and happy coding\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#player-scrollbar","title":"Player Scrollbar"},{"url":"#double-extension","title":"Double Extension"},{"url":"#player-progressview","title":"Player ProgressView"},{"url":"#expandedplayercomponent","title":"ExpandedPlayerComponent"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hello ladies and gents, welcome back to lesson # 29.  In the previous one, we started working on the player view, and in this one, we will continue from where we left off. Let's jump right into it.\"), mdx(\"h2\", null, \"Player Scrollbar\"), mdx(\"p\", null, \"This scrollbar will only be visible on the the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ExpandedPlayerComponent\"), \" which will be created shortly.\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI \\u2192 Components \\u2192 Player\")), \" folder, add a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Scrollbar.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Only check the iOS target\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson29Scrollbar2\\n// $$liquidcoderFilename-Scrollbar\\n\\nimport SwiftUI\\n\\nstruct Scrollbar: View {\\n\\n    // highlight-start\\n    @Binding var currentTime: Double\\n    var totalTime: Double\\n    var onFinish: ((Double) -> Void) = {_ in }\\n    // highlight-end\\n\\n    // highlight-start\\n    @State private var offsetX: CGFloat = 0.0\\n    @State private var percent: CGFloat = 0.0\\n    @State private var timeIndicatorOffsetX: CGFloat = 0\\n    @State private var isChanging: Bool = false\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct Scrollbar_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            Scrollbar(currentTime: .constant(2000), totalTime: 3000)\\n                .preferredColorScheme(.dark)\\n                                .previewLayout(.fixed(width: 350.0, height: 50))\\n            Scrollbar(currentTime: .constant(2000), totalTime: 3000)\\n                                .previewLayout(.fixed(width: 350.0, height: 50))\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"We first declare properties that we will receive from the calling site as well as the local state properties.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson29Scrollbar3\\n// $$liquidcoderFilename-Scrollbar\\n\\nimport SwiftUI\\n\\nstruct Scrollbar: View {\\n\\n    @Binding var currentTime: Double\\n    var totalTime: Double\\n    var onFinish: ((Double) -> Void) = {_ in }\\n\\n    @State private var offsetX: CGFloat = 0.0\\n    @State private var percent: CGFloat = 0.0\\n    @State private var timeIndicatorOffsetX: CGFloat = 0\\n    @State private var isChanging: Bool = false\\n\\n    var body: some View {\\n        // highlight-start\\n        GeometryReader { geometry in\\n            ZStack(alignment: .leading) {\\n                RoundedRectangle(cornerSize: CGSize(width: 5, height: 5))\\n                    .foregroundColor(Color.accentColor.opacity(0.3))\\n                    .frame(width: nil, height: geometry.size.height, alignment: .center)\\n\\n                RoundedRectangle(cornerSize: CGSize(width: 3, height: 3))\\n                    .foregroundColor(Color.accentColor)\\n                    .frame(width: offsetX, height: geometry.size.height, alignment: .center)\\n            }\\n        }\\n        // highlight-end\\n    }\\n}\\n\\nstruct Scrollbar_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            Scrollbar(currentTime: .constant(2000), totalTime: 3000)\\n                .previewLayout(.fixed(width: 350.0, height: 50))\\n                .preferredColorScheme(.dark)\\n            Scrollbar(currentTime: .constant(2000), totalTime: 3000)\\n                .previewLayout(.fixed(width: 350.0, height: 50))\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"We then create the interactive bar that will be used to update the player's current time as well as change the current time using a similar trick we used when creating the circular progress view stacking 2 shape on the z-axis.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson29Scrollbar4\\n// $$liquidcoderFilename-Scrollbar\\n\\nimport SwiftUI\\n\\nstruct Scrollbar: View {\\n\\n    @Binding var currentTime: Double\\n    var totalTime: Double\\n    var onFinish: ((Double) -> Void) = {_ in }\\n\\n    @State private var offsetX: CGFloat = 0.0\\n    @State private var percent: CGFloat = 0.0\\n    @State private var timeIndicatorOffsetX: CGFloat = 0\\n    @State private var isChanging: Bool = false\\n\\n    var body: some View {\\n        GeometryReader { geometry in\\n            ZStack(alignment: .leading) {\\n                RoundedRectangle(cornerSize: CGSize(width: 5, height: 5))\\n                    .foregroundColor(Color.accentColor.opacity(0.3))\\n                    .frame(width: nil, height: geometry.size.height, alignment: .center)\\n\\n                RoundedRectangle(cornerSize: CGSize(width: 3, height: 3))\\n                    .foregroundColor(Color.accentColor)\\n                    .frame(width: offsetX, height: geometry.size.height, alignment: .leading)\\n                // highlight-start\\n            }.gesture(\\n                DragGesture()\\n                    .onChanged({ drag in\\n                        self.offsetX = min(max(drag.location.x, 0), geometry.size.width)\\n                        self.percent = self.offsetX / (geometry.size.width)\\n                    }).onEnded({ drag in\\n                        self.offsetX = min(max(drag.location.x, 0), geometry.size.width)\\n                    })\\n            )\\n            // highlight-end\\n        }\\n    }\\n}\\n\\nstruct Scrollbar_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            Scrollbar(currentTime: .constant(2000), totalTime: 3000)\\n                .previewLayout(.fixed(width: 350.0, height: 50))\\n                .preferredColorScheme(.dark)\\n            Scrollbar(currentTime: .constant(2000), totalTime: 3000)\\n                .previewLayout(.fixed(width: 350.0, height: 50))\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"We attach a gesture modifier to make the bar interactive. \"), mdx(\"p\", null, \"We set the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"offsetX\"), \" to move between the width of the entire view and zero that's what the min and max functions do. \"), mdx(\"h2\", null, \"Double Extension\"), mdx(\"p\", null, \"Let's create this extension on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Double\"), \" type  in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"Extensions\")), \" folder before continuing with our scrollbar.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson29DoubleExt1\\n// $$liquidcoderFilename-Double+\\n\\nimport Foundation\\n\\nextension Double {\\n  func asString(style: DateComponentsFormatter.UnitsStyle = .positional) -> String {\\n    let formatter = DateComponentsFormatter()\\n    formatter.allowedUnits = self >= 3600 ? [.hour, .minute, .second] : [.minute, .second]\\n    formatter.unitsStyle = style\\n    formatter.zeroFormattingBehavior = .pad\\n    guard let formattedString = formatter.string(from: self) else { return \\\"\\\" }\\n    return formattedString\\n  }\\n}\\n\")), mdx(\"p\", null, \"Here we are formatting a double value to time format.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson29Scrollbar5\\n// $$liquidcoderFilename-Scrollbar\\n\\nimport SwiftUI\\n\\nstruct Scrollbar: View {\\n\\n    @Binding var currentTime: Double\\n    var totalTime: Double\\n    var onFinish: ((Double) -> Void) = {_ in }\\n\\n    @State private var offsetX: CGFloat = 0.0\\n    @State private var percent: CGFloat = 0.0\\n    @State private var timeIndicatorOffsetX: CGFloat = 0\\n    @State private var isChanging: Bool = false\\n\\n    var body: some View {\\n        GeometryReader { geometry in\\n            ZStack(alignment: .leading) {\\n                RoundedRectangle(cornerSize: CGSize(width: 5, height: 5))\\n                    .foregroundColor(Color.accentColor.opacity(0.3))\\n                    .frame(width: nil, height: geometry.size.height, alignment: .center)\\n\\n                RoundedRectangle(cornerSize: CGSize(width: 3, height: 3))\\n                    .foregroundColor(Color.accentColor)\\n                    .frame(width: offsetX, height: geometry.size.height, alignment: .leading)\\n\\n                // highlight-start\\n                if isChanging {\\n                    Text((Double(percent) * totalTime)\\n                            .asString(style: .positional))\\n                        .font(.caption)\\n                        .offset(x:  timeIndicatorOffsetX, y: -geometry.size.height)\\n                }\\n                // highlight-end\\n            }.gesture(\\n                DragGesture()\\n                    .onChanged({ drag in\\n                        self.offsetX = min(max(drag.location.x, 0), geometry.size.width)\\n                        self.percent = self.offsetX / (geometry.size.width)\\n                        // highlight-start\\n                        updateIndicatorOffset()\\n                        if !self.isChanging { self.isChanging.toggle() }\\n                        // highlight-end\\n                    }).onEnded({ drag in\\n                        self.offsetX = min(max(drag.location.x, 0), geometry.size.width)\\n                        // highlight-start\\n                        self.isChanging = false\\n                        // highlight-end\\n                    })\\n            )\\n        }\\n    }\\n\\n    // highlight-start\\n    private func updateIndicatorOffset() {\\n        if percent > 0.8 {\\n            timeIndicatorOffsetX = offsetX - 40\\n        } else if percent < 0.2 {\\n            timeIndicatorOffsetX = offsetX\\n        } else {\\n            timeIndicatorOffsetX = offsetX - 20\\n        }\\n    }\\n    // highlight-end\\n}\\n\")), mdx(\"p\", null, \"We want to show a small text displaying the current time when seeking to a particular position, and we will show that \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" only when \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isChanging\"), \" is \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"true\"), \".\"), mdx(\"p\", null, \"We then update \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isChanging\"), \" whenever the user starts seeking as well as when he finishes. \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\n// $$liquidcoderID-Lesson29Scrollbar6\\n// $$liquidcoderFilename-Scrollbar\\n\\nimport SwiftUI\\n\\nstruct Scrollbar: View {\\n\\n    @Binding var currentTime: Double\\n    var totalTime: Double\\n    var onFinish: ((Double) -> Void) = {_ in }\\n\\n    @State private var offsetX: CGFloat = 0.0\\n    @State private var percent: CGFloat = 0.0\\n    @State private var timeIndicatorOffsetX: CGFloat = 0\\n    @State private var isChanging: Bool = false\\n\\n    var body: some View {\\n        GeometryReader { geometry in\\n            ZStack(alignment: .leading) {\\n                RoundedRectangle(cornerSize: CGSize(width: 5, height: 5))\\n                    .foregroundColor(Color.accentColor.opacity(0.3))\\n                    .frame(width: nil, height: geometry.size.height, alignment: .center)\\n\\n                RoundedRectangle(cornerSize: CGSize(width: 3, height: 3))\\n                    .foregroundColor(Color.accentColor)\\n                    .frame(width: offsetX, height: geometry.size.height, alignment: .leading)\\n\\n                if isChanging {\\n                    Text((Double(percent) * totalTime)\\n                            .asString(style: .positional))\\n                        .font(.caption)\\n                        .offset(x: timeIndicatorOffsetX, y: -geometry.size.height)\\n                }\\n                // highlight-start\\n            }.frame(height: geometry.size.height + 10, alignment: .center)\\n            // highlight-end\\n            .gesture(\\n                DragGesture()\\n                    .onChanged({ drag in\\n                        self.offsetX = min(max(drag.location.x, 0), geometry.size.width)\\n                        self.percent = self.offsetX / (geometry.size.width)\\n                        updateIndicatorOffset()\\n                        if !self.isChanging { self.isChanging.toggle() }\\n                    }).onEnded({ drag in\\n                        self.offsetX = min(max(drag.location.x, 0), geometry.size.width)\\n                        // highlight-start\\n                        onFinish(Double(offsetX / (geometry.size.width)))\\n                        // highlight-end\\n                        self.isChanging = false\\n                    })\\n                // highlight-start\\n            )\\n            .onChange(of: currentTime, perform: { value in\\n                self.percent = CGFloat(value / totalTime)\\n                self.offsetX = percent * geometry.size.width\\n            })\\n            // highlight-end\\n        }\\n    }\\n\\n    private func updateIndicatorOffset() {\\n        if percent > 0.8 {\\n            timeIndicatorOffsetX = offsetX - 40\\n        } else if percent < 0.2 {\\n            timeIndicatorOffsetX = offsetX\\n        } else {\\n            timeIndicatorOffsetX = offsetX - 20\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"Last for this component, we notify the calling view that we've finished seeking by calling the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onFinish\"), \" closure.\"), mdx(\"hr\", null), mdx(\"p\", null, \"We also update the percent and the offset when \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"currentTime\"), \" changes to update the player progress.\"), mdx(\"h2\", null, \"Player ProgressView\"), mdx(\"p\", null, \"This view will utilise the scrollbar to create a component that will be used in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ExpandedPlayerComponent\")), mdx(\"p\", null, \"In the same folder, add a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PlayerProgressView.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Only check the iOS target\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson29PlayerProgressView2\\n// $$liquidcoderFilename-PlayerProgressView\\n\\nimport SwiftUI\\n\\nstruct PlayerProgressView: View {\\n\\n    // highlight-start\\n    var currentTime: Double\\n    var totalTime: Double\\n    var onFinishSeeking: ((Double) -> Void) = { _ in }\\n    // highlight-end\\n\\n    var body: some View {\\n        // highlight-start\\n        VStack {\\n             Scrollbar(currentTime: .constant(currentTime),\\n                      totalTime: totalTime, onFinish:  { value in\\n                onFinishSeeking(totalTime * value)\\n                      }).frame(height: 15, alignment: .center)\\n\\n            HStack{\\n                Text(\\\"\\\\(currentTime.asString(style: .positional))\\\")\\n                    .font(.caption)\\n                Spacer()\\n                Text(\\\"\\\\(totalTime.asString(style: .positional))\\\")\\n                    .font(.caption)\\n            }\\n\\n        }.frame(height: 40, alignment: .center)\\n        // highlight-end\\n    }\\n}\\n\\nstruct PlayerProgressView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            PlayerProgressView(currentTime: 2000, totalTime: 3000)\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PlayerProgressView(currentTime: 2000, totalTime: 3000)\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"This view is simpler than it looks. We just stack the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Scrollbar\"), \" with the 2 \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Text\"), \" views vertically.\"), mdx(\"h2\", null, \"ExpandedPlayerComponent\"), mdx(\"p\", null, \"In the same folder, add a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"ExpandedPlayerComponent.\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Only check the iOS target\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson28ExpandedPlayerComponent2\\n// $$liquidcoderFilename-ExpandedPlayerComponent\\n\\nimport SwiftUI\\n\\nstruct ExpandedPlayerComponent: View {\\n\\n    // highlight-start\\n    var track: Track\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct ExpandedPlayerComponent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            ExpandedPlayerComponent(track: Track.placeholder)\\n                .previewLayout(.sizeThatFits)\\n            ExpandedPlayerComponent(track: Track.placeholder)\\n                .previewLayout(.sizeThatFits)\\n        }\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"Just like folded one, we also need a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"track\"), \" here.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson28ExpandedPlayerComponent3\\n// $$liquidcoderFilename-ExpandedPlayerComponent\\n\\nimport SwiftUI\\n\\nstruct ExpandedPlayerComponent: View {\\n\\n    var track: Track\\n\\n    var body: some View {\\n        // highlight-start\\n        VStack(spacing: 40) {\\n            TrackImageView(track: track)\\n                .frame(width: 200, height: 200, alignment: .center)\\n                .cornerRadius(15)\\n\\n            VStack {\\n                Text(track.title)\\n                    .font(.callout)\\n                    .lineLimit(2)\\n                    .multilineTextAlignment(.center)\\n                Text(track.podcastName)\\n                    .lineLimit(2)\\n                    .foregroundColor(.gray)\\n            }\\n\\n            PlayerProgressView(currentTime: 200, totalTime: 300)\\n                                    .frame(maxWidth: 300, alignment: .center)            \\n        }.padding(40)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"Here, we stack the image, titles and the progress view vertically\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson28ExpandedPlayerComponent4\\n// $$liquidcoderFilename-ExpandedPlayerComponent\\n\\nimport SwiftUI\\n\\nstruct ExpandedPlayerComponent: View {\\n\\n    var track: Track\\n\\n    var body: some View {\\n        VStack(spacing: 40) {\\n            TrackImageView(track: track)\\n                .frame(width: 200, height: 200, alignment: .center)\\n                .cornerRadius(15)\\n\\n            VStack {\\n                Text(track.title)\\n                    .font(.callout)\\n                    .lineLimit(2)\\n                    .multilineTextAlignment(.center)\\n                Text(track.podcastName)\\n                    .lineLimit(2)\\n                    .foregroundColor(.gray)\\n            }\\n\\n            PlayerProgressView(currentTime: 200, totalTime: 300)\\n                          .frame(maxWidth: 300, alignment: .center)\\n        // highlight-start\\n            createButtonsRow()\\n        // highlight-end\\n        }.padding(40)\\n    }\\n\\n        // highlight-start\\n    private func createButtonsRow() -> some View {\\n        HStack(spacing: 50) {\\n            PlayerButton(action: {\\n\\n            }, icon: \\\"gobackward.15\\\", isBig: true)\\n            .cornerRadius(4)\\n\\n            PlayerButton(action: {\\n\\n            }, icon: \\\"play.fill\\\", isBig: true)\\n\\n            PlayerButton(action: {\\n\\n            }, icon:  \\\"goforward.15\\\", isBig: true)\\n            .cornerRadius(4)\\n\\n        }\\n    }\\n        // highlight-end\\n}\\n\")), mdx(\"p\", null, \"Next, we create the buttons row which consists of a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HStack\"), \" containing 3 instances of \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PlayerButton\"), \" that will control the player.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson29ExpandedPlayerComponent5\\n// $$liquidcoderFilename-ExpandedPlayerComponent\\n\\nimport SwiftUI\\n\\nstruct ExpandedPlayerComponent: View {\\n\\n    var track: Track\\n    // highlight-start\\n    @Binding var isExpanded: Bool\\n    @Binding var offsetY: CGFloat\\n    private let threshold: CGFloat = 100\\n    // highlight-end\\n\\n    var body: some View {\\n        VStack(spacing: 40) {\\n            TrackImageView(track: track)\\n                .frame(width: 200, height: 200, alignment: .center)\\n                .cornerRadius(15)\\n\\n            VStack {\\n                Text(track.title)\\n                    .font(.callout)\\n                    .lineLimit(2)\\n                    .multilineTextAlignment(.center)\\n                Text(track.podcastName)\\n                    .lineLimit(2)\\n                    .foregroundColor(.gray)\\n            }\\n\\n            PlayerProgressView(currentTime: 200, totalTime: 300)\\n                                    .frame(maxWidth: 300, alignment: .center)\\n            createButtonsRow()\\n        }.padding(40)\\n        // highlight-start\\n        .offset(x: 0, y: self.offsetY)\\n        .gesture(\\n            DragGesture().onChanged({ value in\\n                    self.offsetY = value.translation.height > 0 ? value.translation.height / 2 : 0\\n            }).onEnded({ value in\\n                if value.translation.height > self.threshold {\\n                    isExpanded = false\\n                }\\n            })\\n\\n        )\\n        // highlight-end\\n    }\\n\\n    private func createButtonsRow() -> some View {\\n        ...\\n    }\\n}\\n\\nstruct ExpandedPlayerComponent_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            // highlight-start\\n            ExpandedPlayerComponent(track: Track.placeholder, isExpanded: .constant(false), offsetY: .constant(0))\\n                // highlight-end\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            // highlight-start\\n            ExpandedPlayerComponent(track: Track.placeholder, isExpanded: .constant(false), offsetY: .constant(0))\\n                // highlight-end\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"Just like the folded one, the expanded component also need to control the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isExpanded\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"offsetY\"), \" properties.\"), mdx(\"p\", null, \"We then add an \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"offset\"), \" and a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DragGesture\"), \" to the entire component to make it dismissible. We divide the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"value.translation.height\"), \" by 2 to create a little resistance when dismissing.\"), mdx(\"p\", null, \"And this brings this lesson to a close. In the next one, we will create the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PlayerView\"), \" which will put these 2 components together to create an amazing interactive floating player component. See you then and happy coding\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#player-scrollbar","title":"Player Scrollbar"},{"url":"#double-extension","title":"Double Extension"},{"url":"#player-progressview","title":"Player ProgressView"},{"url":"#expandedplayercomponent","title":"ExpandedPlayerComponent"}]}}}},{"createdDate":"2 years ago","createdAt":"2021-04-07T09:16:29.879Z","isNewLayout":true,"previews":{"previews":"{\n\n\"Lesson30PlayerView1\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\"Lesson30PlayerView2\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\"Lesson30PlayerView3\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1614070336/StreamCast/Shared/grfn8p6fxdrey0t1tuef.png\"],\n\"Lesson30PlayerView4\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1617737945/StreamCast/Lesson%2030/gw8rd2pdzjnswa2zlvac.png\"],\n\"Lesson30PlayerView5\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1617737728/StreamCast/Lesson%2030/iuzphylg5d9theswbtgg.gif\"],\n\"Lesson30UpdateMainScreen1\": [\"https://res.cloudinary.com/liquidcoder/image/upload/v1617738497/StreamCast/Lesson%2030/el1astwwzjhjhf6fyas0.gif\"]\n}"},"slug":"lesson-30-playerview-part-3","title":"Lesson #30: PlayerView Part 3","updatedDate":"2021-04-07T00:00+01:00","downloads":[{"url":"https://u.pcloud.link/publink/show?code=XZlvNWXZ2cGQAKEaOPRWnSxcQVB3nk3xoDay","type":"zip","id":"f1d7a108-702b-541d-9e05-18242509a1cc","name":"Source code"}],"allChapters":null,"code":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson30BlurView1\\n// $$liquidcoderFilename-BlurView\\n\\nimport SwiftUI\\n\\nstruct BlurView: UIViewRepresentable {\\n\\n    var effect = UIBlurEffect(style: .regular)\\n\\n    func makeUIView(context: UIViewRepresentableContext<Self>) -> UIVisualEffectView {\\n        UIVisualEffectView()\\n    }\\n\\n    func updateUIView(_ uiView: UIVisualEffectView, context: UIViewRepresentableContext<Self>) {\\n        uiView.effect = effect\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson30PlayerView1\\n// $$liquidcoderFilename-PlayerView\\n\\nimport SwiftUI\\n\\nstruct PlayerView: View {\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct PlayerView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        PlayerView()\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson30PlayerView2\\n// $$liquidcoderFilename-PlayerView\\n\\nimport SwiftUI\\n\\nstruct PlayerView: View {\\n\\n    // highlight-start\\n    @State private var isExpanded = false\\n    @State private var offsetY: CGFloat = 0\\n    // highlight-end\\n\\n    // highlight-start\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?\\n    @EnvironmentObject private var playerManager: PlayerManager\\n    // highlight-end\\n\\n    // highlight-start\\n    @Namespace private var animation\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct PlayerView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            PlayerView()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PlayerView()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(PlayerManager())\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson30PlayerView3\\n// $$liquidcoderFilename-PlayerView\\n\\nimport SwiftUI\\n\\nstruct PlayerView: View {\\n\\n    @State private var isExpanded = false\\n    @State private var offsetY: CGFloat = 0\\n    private let threshold: CGFloat = 100\\n\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?\\n    @EnvironmentObject private var playerManager: PlayerManager\\n\\n        @Namespace private var animation\\n\\n    var body: some View {\\n        // highlight-start\\n        switch playerManager.state {\\n        case .idle:\\n            createContent(track: playerManager.currentTrack)\\n                .redacted(reason: .placeholder)\\n        case .loading:\\n            ProgressView(\\\"Loading...\\\")\\n                .padding()\\n        case .playing:\\n            createContent(track: playerManager.currentTrack)\\n        case .failed(let message):\\n            Text(message)\\n        }\\n        // highlight-end\\n    }\\n\\n    // highlight-start\\n    func createContent(track: Track) -> some View {\\n        EmptyView()\\n    }\\n    // highlight-end\\n}\\n\\nstruct PlayerView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            PlayerView()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PlayerView()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(PlayerManager())\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson30PlayerView4\\n// $$liquidcoderFilename-PlayerView\\n\\nimport SwiftUI\\n\\nstruct PlayerView: View {\\n\\n    @State private var isExpanded = false\\n    @State private var offsetY: CGFloat = 0\\n\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?    \\n    @EnvironmentObject private var playerManager: PlayerManager\\n\\n        @Namespace private var animation\\n\\n    var body: some View {\\n        switch playerManager.state {\\n        case .idle:\\n            createContent(track: playerManager.currentTrack)\\n                .redacted(reason: .placeholder)\\n        case .loading:\\n            ProgressView(\\\"Loading...\\\")\\n                .padding()\\n        case .playing:\\n            createContent(track: playerManager.currentTrack)\\n        case .failed(let message):\\n            Text(message)\\n        }\\n    }\\n\\n    func createContent(track: Track) -> some View {\\n        // highlight-start\\n        ZStack(alignment: .bottom, content: {\\n            if isExpanded {\\n                ExpandedPlayerComponent(track: track, isExpanded: $isExpanded, offsetY: $offsetY, namespace: animation)\\n            } else {\\n                FoldedPlayerComponent(track: track, isExpanded: $isExpanded, offsetY: $offsetY)\\n            }\\n        }).background(BlurView())\\n        .cornerRadius(20)\\n        .roundedBorder(radius: 20, color: Color.border)\\n        .padding()\\n        .padding(.bottom, 10)\\n        // highlight-end\\n    }\\n}\\n\\nstruct PlayerView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            PlayerView()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PlayerView()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(PlayerManager())\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson30ExpandedPlayerComponent1\\n// $$liquidcoderFilename-ExpandedPlayerComponent\\n\\nimport SwiftUI\\n\\nstruct ExpandedPlayerComponent: View {\\n\\n    var track: Track\\n\\n    @Binding var isExpanded: Bool\\n    @Binding var offsetY: CGFloat\\n    private let threshold: CGFloat = 100\\n    // highlight-start\\n    var namespace: Namespace.ID\\n    // highlight-end\\n\\n    var body: some View {\\n        VStack(spacing: 40) {\\n            TrackImageView(track: track)\\n                .cornerRadius(15)\\n                // highlight-start\\n                .matchedGeometryEffect(id: \\\"\\\\(track.title)-image\\\", in: namespace)\\n                // highlight-end\\n                .frame(width: 200, height: 200, alignment: .center)\\n\\n            VStack {\\n                Text(track.title)\\n                    .font(.callout)\\n                    .lineLimit(2)\\n                    .multilineTextAlignment(.center)\\n                Text(track.podcastName)\\n                    .lineLimit(2)\\n                    .foregroundColor(.gray)\\n                            // highlight-start\\n            }.matchedGeometryEffect(id: \\\"\\\\(track.title)-titles\\\" ,\\n                                    in: namespace)\\n            // highlight-end\\n\\n            PlayerProgressView(currentTime: 200, totalTime: 300)\\n                                // highlight-start\\n                .matchedGeometryEffect(id: \\\"\\\\(track.title)-progressView\\\", in: namespace)\\n                // highlight-end\\n                        .frame(maxWidth: 300, alignment: .center)\\n            createButtonsRow()\\n        }.padding(40)\\n        .offset(x: 0, y: self.offsetY)\\n        .gesture(\\n            DragGesture().onChanged({ value in\\n                    self.offsetY = value.translation.height > 0 ? value.translation.height / 2 : 0\\n            }).onEnded({ value in\\n                if value.translation.height > self.threshold {\\n                    isExpanded = false\\n                }\\n            })\\n\\n        )\\n    }\\n\\n    private func createButtonsRow() -> some View {\\n        HStack(spacing: 50) {\\n            PlayerButton(action: {\\n\\n            }, icon: \\\"gobackward.15\\\", isBig: true)\\n            .cornerRadius(4)\\n            // highlight-start\\n                        .matchedGeometryEffect(id: \\\"\\\\(track.title)-backward\\\", in: namespace)\\n            // highlight-end        \\n\\n            PlayerButton(action: {\\n\\n            }, icon: \\\"play.fill\\\", isBig: true)\\n                        .cornerRadius(4)\\n            // highlight-start\\n            .matchedGeometryEffect(id: \\\"\\\\(track.title)-play\\\" , in: namespace)\\n            // highlight-end\\n\\n            PlayerButton(action: {\\n\\n            }, icon: \\\"goforward.15\\\", isBig: true)\\n            .cornerRadius(4)\\n            // highlight-start\\n            .matchedGeometryEffect(id: \\\"\\\\(track.title)-forward\\\" , in: namespace)\\n            // highlight-end\\n\\n        }\\n    }\\n}\\n\\nstruct ExpandedPlayerComponent_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    @Namespace private static var animation\\n    // highlight-end\\n\\n    static var previews: some View {\\n        Group {\\n            // highlight-start\\n            ExpandedPlayerComponent(track: Track.placeholder, isExpanded: .constant(false), offsetY: .constant(0), namespace: animation)\\n                // highlight-end\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            // highlight-start\\n            ExpandedPlayerComponent(track: Track.placeholder, isExpanded: .constant(false), offsetY: .constant(0), namespace: animation)\\n                // highlight-end\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson30FoldedPlayerComponent1\\n// $$liquidcoderFilename-FoldedPlayerComponent\\n\\nimport SwiftUI\\n\\nstruct FoldedPlayerComponent: View {\\n\\n    var track: Track\\n\\n    @Binding var isExpanded: Bool\\n    @Binding var offsetY: CGFloat\\n\\n    // highlight-start\\n    var namespace: Namespace.ID\\n    // highlight-end\\n\\n    var body: some View {\\n        HStack {\\n            TrackImageView(track: track)\\n                .cornerRadius(10)\\n                // highlight-start\\n                .matchedGeometryEffect(id: \\\"\\\\(track.title)-image\\\", in: namespace)\\n                // highlight-end\\n                .frame(width: 70, height: 70)\\n\\n            VStack(alignment: .leading, spacing: 10) {\\n\\n                Text(track.title)\\n                    .font(.callout)\\n                    .lineLimit(1)\\n                // highlight-start\\n                                        .matchedGeometryEffect(id: \\\"\\\\(track.title)-progressView\\\" ,\\n                                            in: namespace)\\n                // highlight-end\\n\\n                Text(track.podcastName)\\n                    .font(.footnote)\\n                    .lineLimit(1)\\n                    .foregroundColor(.gray)\\n            // highlight-start\\n            }.matchedGeometryEffect(id: \\\"\\\\(track.title)-titles\\\" ,\\n                                    in: namespace)\\n            // highlight-end\\n\\n            PlayerButton(action: {}, icon: \\\"play.fill\\\", isBig: false)\\n                // highlight-start\\n                .matchedGeometryEffect(id: \\\"\\\\(track.title)-play\\\", in: namespace)\\n                .matchedGeometryEffect(id: \\\"\\\\(track.title)-backward\\\" , in: namespace)\\n                .matchedGeometryEffect(id: \\\"\\\\(track.title)-forward\\\" , in: namespace)\\n                // highlight-end\\n                .frame(maxHeight: .infinity)\\n        }.padding(10)\\n                .onTapGesture {\\n            self.isExpanded = true\\n            self.offsetY = 0\\n        }\\n    }\\n}\\n\\nstruct FoldedPlayerComponent_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    @Namespace private static var namespace\\n    // highlight-end\\n\\n    static var previews: some View {\\n        Group {\\n            // highlight-start\\n            FoldedPlayerComponent(track: Track.placeholder, isExpanded: .constant(false), offsetY: .constant(0), namespace: namespace)\\n                // highlight-end\\n                .previewLayout(.sizeThatFits)\\n                .preferredColorScheme(.dark)\\n            // highlight-start\\n            FoldedPlayerComponent(track: Track.placeholder, isExpanded: .constant(false), offsetY: .constant(0), namespace: namespace)\\n                // highlight-end\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson30PlayerView5\\n// $$liquidcoderFilename-PlayerView\\n\\nimport SwiftUI\\n\\nstruct PlayerView: View {\\n\\n    @State private var isExpanded = true\\n    @State private var offsetY: CGFloat = 0\\n\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?\\n    @EnvironmentObject private var playerManager: PlayerManager\\n\\n    @Namespace private var animation\\n\\n    var body: some View {\\n        switch playerManager.state {\\n        case .idle(let placeholder):\\n            createContent(track: placeholder)\\n                .redacted(reason: .placeholder)\\n        case .loading:\\n            ProgressView(\\\"Loading...\\\")\\n                .padding()\\n        case .playing(let track):\\n            createContent(track: track)\\n        case .failed(let message):\\n            Text(message)\\n        }\\n    }\\n\\n    func createContent(track: Track) -> some View {\\n        ZStack(alignment: .bottom, content: {\\n            if isExpanded {\\n// highlight-start\\n                ExpandedPlayerComponent(track: track, isExpanded: $isExpanded, offsetY: $offsetY, namespace: animation)\\n// highlight-end\\n            } else {\\n// highlight-start\\n                FoldedPlayerComponent(track: track, isExpanded: $isExpanded, offsetY: $offsetY, namespace: animation )\\n// highlight-end\\n            }\\n        }).background(BlurView())\\n        .cornerRadius(20)\\n        .roundedBorder(radius: 20, color: Color.border)\\n        .padding()\\n        .padding(.bottom, 10)\\n        .animation(.spring(response: 0.55, dampingFraction: 0.9, blendDuration: 0), value: self.isExpanded)\\n    }\\n}\\n\\nstruct PlayerView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            PlayerView()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PlayerView()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(PlayerManager())\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson30UpdateMainScreen1\\n// $$liquidcoderFilename-MainScreen\\n\\nimport SwiftUI\\n\\nstruct MainScreen: View {\\n\\n    #if os(iOS)\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?\\n    #endif\\n\\n    var body: some View {\\n        // highlight-start\\n        ZStack(alignment: alignment()) {\\n        // highlight-end\\n            NavigationView {\\n                if hSizeClass == .regular {\\n                   Sidebar()\\n                } else {\\n                    HomeScreen()\\n                }\\n            }\\n                    // highlight-start\\n            PlayerView()\\n        // highlight-end\\n\\n        }.edgesIgnoringSafeArea(.all)\\n    }\\n\\n    // highlight-start\\n    func alignment() -> Alignment {\\n        switch hSizeClass {\\n        case .regular:\\n            return .bottomTrailing\\n        case .compact:\\n            return .bottom\\n        default:\\n            return .bottom\\n        }\\n    }\\n    // highlight-end\\n}\\n\\nstruct MainScreen_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n    static let playerManager = PlayerManager()\\n        static let downloadManager = DownloadManager()\\n    // highlight-end\\n\\n    static var previews: some View {\\n        Group {\\n            MainScreen()\\n                .preferredColorScheme(.dark)\\n            MainScreen()\\n            // highlight-start\\n        }.environmentObject(store)\\n        .environmentObject(playerManager)\\n                .environmentObject(downloadManager)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson30UpdateStreamCastApp1\\n// $$liquidcoderFilename-StreamCastApp\\n\\nimport SwiftUI\\n\\n@main\\nstruct StreamCastApp: App {\\n    let persistenceController = PersistenceController.shared\\n    @ObservedObject private var store = Store()\\n    @ObservedObject private var downloadManager = DownloadManager()\\n    // highlight-start\\n    @ObservedObject private var playerManager = PlayerManager()\\n    // highlight-end\\n\\n    var body: some Scene {\\n        WindowGroup {\\n            ContentView()\\n                .environmentObject(store)\\n                .environmentObject(downloadManager)\\n                // highlight-start\\n                .environmentObject(playerManager)\\n                // highlight-end\\n                .environment(\\\\.managedObjectContext, persistenceController.container.viewContext)\\n        }\\n    }\\n}\\n\")));\n}\n;\nMDXContent.isMDXComponent = true;"}},"explanation":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"div\", {\n    id: \"Lesson29Intro-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey guys, welcome back to lesson #30. In the previous, we worked on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ExpandedPlayerComponent\"), \", and in this one, we create the player view. Let's get started.\")), mdx(\"div\", {\n    id: \"Lesson30BlurView0-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"BlurView\"), mdx(\"p\", null, \"This is the first and hopefully the only UIKit component we will have in the project, and maybe will eventually be changed with a native swiftUI equivalent.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS target only\"))), mdx(\"div\", {\n    id: \"Lesson30BlurView1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Create a swiftUI file in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI \\u2192 Elements\")), \" folder containing the following code.\"), mdx(\"hr\", null), mdx(\"p\", null, \"We are conforming the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"BlurView\"), \"  struct to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"UIViewRepresentable\"), \" in order to be used in swiftUI views.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Learn more about  UIViewRepresentable  \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://developer.apple.com/documentation/swiftui/uiviewrepresentable\"\n  }), \"here\"), \" and \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://www.hackingwithswift.com/quick-start/swiftui/how-to-create-modifiers-for-a-uiviewrepresentable-struct\"\n  }), \"here\")))), mdx(\"div\", {\n    id: \"Lesson30PlayerView1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"PlayerView\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ShareUI \\u2192 Components \\u2192 Player\")), \" folder, create a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PlayerView\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS target only\"))), mdx(\"div\", {\n    id: \"Lesson30PlayerView2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We first declare properties that will be used shortly. The namespace animation will be used to synchronise transition when folding or expanding the player component.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"We need to attach a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PlayerManager\"), \" instance in the preview environment for it to work.\"))), mdx(\"div\", {\n    id: \"Lesson30PlayerView3-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We switch on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"playerManager\"), \"'s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" in order to create appropriate content  for each state value.\"), mdx(\"p\", null, \"Don't worry about the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EmptyView\"), \" in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createContent\"), \" function, it will be replace in a minute.\")), mdx(\"div\", {\n    id: \"Lesson30PlayerView4-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"We return an appropriate player component based on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isExpanded\"), \" property. We then add a blurred background using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"BlurView\"), \" created earlier, and apply more styles to the entire container.\")), mdx(\"div\", {\n    id: \"Lesson30ExpandedPlayerComponent1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Update ExpandedPlayerComponent\"), mdx(\"p\", null, \"We use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"matchedGeometryEffect\"), \" to sync animation transition between controls from both the expanded and folded components.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"I have written an overview article about the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"matchedGeometryEffect\"), \" modifier, find it \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://liquidcoder.com/article/matchedgeometryeffect-overview\"\n  }), \"here\"), \".\"))), mdx(\"div\", {\n    id: \"Lesson30ExpandedPlayerComponent11-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"This view is getting messy, and desperately needs refactoring. I have done this on purpose  to let you do it  by yourself.  Doing this will actually help you understand the code better when you refactor the view yourself.\")), mdx(\"div\", {\n    id: \"Lesson30FoldedPlayerComponent1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Update FoldedPlayerComponent\"), mdx(\"p\", null, \"Just like the expanded component, we add the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"matchedGeometryEffect\"), \" modifier to views that we want their transitions synchronised with the ones in the expanded component.\")), mdx(\"div\", {\n    id: \"Lesson30FoldedPlayerComponent2-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Notice how we add 3 \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"matchedGeometryEffect\"), \" modifiers to a single view pointing to 3 different views. This will create an effect where the 3 buttons from the expanded player component transition from this single button.\")), mdx(\"div\", {\n    id: \"Lesson30PlayerView5-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Now that we have introduced another property in the 3 views, let's update their respective call site to add the missing namespace parameter.\")), mdx(\"div\", {\n    id: \"Lesson30UpdateMainScreen1-Section\",\n    className: \"post-section\"\n  }, mdx(\"h2\", null, \"Update MainScreen\"), mdx(\"p\", null, \"We want the player component to be aligned to the right edge of the screen on larger displays, and be centered on narrow ones that's why we create that \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"alignment\"), \" function that returns appropriate \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Alignment\"), \" value based on the horizontal size class\")), mdx(\"div\", {\n    id: \"Lesson30UpdateStreamCastApp1-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"Last, we attach a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PlayerManager\"), \" object to the content view in order to run the app.\")), mdx(\"div\", {\n    id: \"Lesson30Outro-Section\",\n    className: \"post-section\"\n  }, mdx(\"p\", null, \"And this brings this lesson to a close, in the next one we will work on playing the actual episodes. See you then and happy coding!.\")));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#blurview","title":"BlurView"},{"url":"#playerview","title":"PlayerView"},{"url":"#update-expandedplayercomponent","title":"Update ExpandedPlayerComponent"},{"url":"#update-foldedplayercomponent","title":"Update FoldedPlayerComponent"},{"url":"#update-mainscreen","title":"Update MainScreen"}]}}},"script":{"childMdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Hey guys, welcome back to lesson #30. In the previous, we worked on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ExpandedPlayerComponent\"), \", and in this one, we create the player view. Let's get started.\"), mdx(\"h2\", null, \"BlurView\"), mdx(\"p\", null, \"This is the first and hopefully the only UIKit component we will have in the project, and maybe will eventually be changed with a native swiftUI equivalent.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS target only\")), mdx(\"p\", null, \"Create a swiftUI file in the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"SharedUI \\u2192 Elements\")), \" folder containing the following code.\"), mdx(\"p\", null, \"We are conforming the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"BlurView\"), \"  struct to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"UIViewRepresentable\"), \" in order to be used in swiftUI views.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Learn more about  UIViewRepresentable  \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://developer.apple.com/documentation/swiftui/uiviewrepresentable\"\n  }), \"here\"), \" and \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://www.hackingwithswift.com/quick-start/swiftui/how-to-create-modifiers-for-a-uiviewrepresentable-struct\"\n  }), \"here\"))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson30BlurView1\\n// $$liquidcoderFilename-BlurView\\n\\nimport SwiftUI\\n\\nstruct BlurView: UIViewRepresentable {\\n\\n    var effect = UIBlurEffect(style: .regular)\\n\\n    func makeUIView(context: UIViewRepresentableContext<Self>) -> UIVisualEffectView {\\n        UIVisualEffectView()\\n    }\\n\\n    func updateUIView(_ uiView: UIVisualEffectView, context: UIViewRepresentableContext<Self>) {\\n        uiView.effect = effect\\n    }\\n}\\n\")), mdx(\"h2\", null, \"PlayerView\"), mdx(\"p\", null, \"In the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, mdx(\"em\", {\n    parentName: \"strong\"\n  }, \"ShareUI \\u2192 Components \\u2192 Player\")), \" folder, create a swiftUI file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"PlayerView\")), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Check the iOS target only\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson30PlayerView2\\n// $$liquidcoderFilename-PlayerView\\n\\nimport SwiftUI\\n\\nstruct PlayerView: View {\\n\\n    // highlight-start\\n    @State private var isExpanded = false\\n    @State private var offsetY: CGFloat = 0\\n    // highlight-end\\n\\n    // highlight-start\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?\\n        @EnvironmentObject private var playerManager: PlayerManager\\n    // highlight-end\\n\\n    // highlight-start\\n        @Namespace private var animation\\n    // highlight-end\\n\\n    var body: some View {\\n        Text(\\\"Hello, World!\\\")\\n    }\\n}\\n\\nstruct PlayerView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        // highlight-start\\n        Group {\\n            PlayerView()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PlayerView()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(PlayerManager())\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"We first declare properties that will be used shortly. The namespace animation will be used to synchronise transition when folding or expanding the player component.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"We need to attach a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PlayerManager\"), \" instance in the preview environment for it to work.\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson30PlayerView3\\n// $$liquidcoderFilename-PlayerView\\n\\nimport SwiftUI\\n\\nstruct PlayerView: View {\\n\\n    @State private var isExpanded = false\\n    @State private var offsetY: CGFloat = 0\\n\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?\\n    @EnvironmentObject private var playerManager: PlayerManager\\n\\n        @Namespace private var animation\\n\\n    var body: some View {\\n        // highlight-start\\n        switch playerManager.state {\\n        case .idle:\\n            createContent(track: playerManager.currentTrack)\\n                .redacted(reason: .placeholder)\\n        case .loading:\\n            ProgressView(\\\"Loading...\\\")\\n                .padding()\\n        case .playing:\\n            createContent(track: playerManager.currentTrack)\\n        case .failed(let message):\\n            Text(message)\\n        }\\n        // highlight-end\\n    }\\n\\n    // highlight-start\\n    func createContent(track: Track) -> some View {\\n        EmptyView()\\n    }\\n    // highlight-end\\n}\\n\\nstruct PlayerView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            PlayerView()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PlayerView()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(PlayerManager())\\n    }\\n}\\n\")), mdx(\"p\", null, \"We switch on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"playerManager\"), \"'s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"state\"), \" in order to create appropriate content  for each state value.\"), mdx(\"p\", null, \"Don't worry about the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EmptyView\"), \" in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createContent\"), \" function, it will be replace in a minute.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson30PlayerView4\\n// $$liquidcoderFilename-PlayerView\\n\\nimport SwiftUI\\n\\nstruct PlayerView: View {\\n\\n    @State private var isExpanded = false\\n    @State private var offsetY: CGFloat = 0\\n\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?    \\n    @EnvironmentObject private var playerManager: PlayerManager\\n\\n        @Namespace private var animation\\n\\n    var body: some View {\\n        switch playerManager.state {\\n        case .idle:\\n            createContent(track: playerManager.currentTrack)\\n                .redacted(reason: .placeholder)\\n        case .loading:\\n            ProgressView(\\\"Loading...\\\")\\n                .padding()\\n        case .playing:\\n            createContent(track: playerManager.currentTrack)\\n        case .failed(let message):\\n            Text(message)\\n        }\\n    }\\n\\n    func createContent(track: Track) -> some View {\\n        // highlight-start\\n        ZStack(alignment: .bottom, content: {\\n            if isExpanded {\\n                ExpandedPlayerComponent(track: track, isExpanded: $isExpanded, offsetY: $offsetY)\\n            } else {\\n                FoldedPlayerComponent(track: track, isExpanded: $isExpanded, offsetY: $offsetY)\\n            }\\n        }).background(BlurView())\\n        .cornerRadius(20)\\n        .roundedBorder(radius: 20, color: Color.border)\\n        .padding()\\n        .padding(.bottom, 10)\\n                .animation(.spring(response: 0.55, dampingFraction: 0.9, blendDuration: 0), value: self.isExpanded)\\n        // highlight-end\\n    }\\n}\\n\\nstruct PlayerView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            PlayerView()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PlayerView()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(PlayerManager())\\n    }\\n}\\n\")), mdx(\"p\", null, \"We return an appropriate player component based on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"isExpanded\"), \" property. We then add a blurred background using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"BlurView\"), \" created earlier, and apply more styles to the entire container.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1617737945/StreamCast/Lesson%2030/gw8rd2pdzjnswa2zlvac.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1617737945/StreamCast/Lesson%2030/gw8rd2pdzjnswa2zlvac.png\"\n  }))), mdx(\"h2\", null, \"Update ExpandedPlayerComponent\"), mdx(\"p\", null, \"We use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"matchedGeometryEffect\"), \" to sync animation transition between controls from both the expanded and folded components.\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"I have written an overview article about the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"matchedGeometryEffect\"), \" modifier, find it \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://liquidcoder.com/article/matchedgeometryeffect-overview\"\n  }), \"here\"), \".\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson30ExpandedPlayerComponent1\\n// $$liquidcoderFilename-ExpandedPlayerComponent\\n\\nimport SwiftUI\\n\\nstruct ExpandedPlayerComponent: View {\\n\\n    var track: Track\\n\\n    @Binding var isExpanded: Bool\\n    @Binding var offsetY: CGFloat\\n    private let threshold: CGFloat = 100\\n    // highlight-start\\n    var namespace: Namespace.ID\\n    // highlight-end\\n\\n    var body: some View {\\n        VStack(spacing: 40) {\\n            TrackImageView(track: track)\\n                .cornerRadius(15)\\n                // highlight-start\\n                .matchedGeometryEffect(id: \\\"\\\\(track.title)-image\\\", in: namespace)\\n                // highlight-end\\n                .frame(width: 200, height: 200, alignment: .center)\\n\\n            VStack {\\n                Text(track.title)\\n                    .font(.callout)\\n                    .lineLimit(2)\\n                    .multilineTextAlignment(.center)\\n                Text(track.podcastName)\\n                    .lineLimit(2)\\n                    .foregroundColor(.gray)\\n                            // highlight-start\\n            }.matchedGeometryEffect(id: \\\"\\\\(track.title)-titles\\\" ,\\n                                    in: namespace)\\n            // highlight-end\\n\\n            PlayerProgressView(currentTime: 200, totalTime: 300)\\n                                // highlight-start\\n                .matchedGeometryEffect(id: \\\"\\\\(track.title)-progressView\\\", in: namespace)\\n                // highlight-end\\n                        .frame(maxWidth: 300, alignment: .center)\\n            createButtonsRow()\\n        }.padding(40)\\n        .offset(x: 0, y: self.offsetY)\\n        .gesture(\\n            DragGesture().onChanged({ value in\\n                    self.offsetY = value.translation.height > 0 ? value.translation.height / 2 : 0\\n            }).onEnded({ value in\\n                if value.translation.height > self.threshold {\\n                    isExpanded = false\\n                }\\n            })\\n\\n        )\\n    }\\n\\n    private func createButtonsRow() -> some View {\\n        HStack(spacing: 50) {\\n            PlayerButton(action: {\\n\\n            }, icon: \\\"gobackward.15\\\", isBig: true)\\n            .cornerRadius(4)\\n            // highlight-start\\n                        .matchedGeometryEffect(id: \\\"\\\\(track.title)-backward\\\", in: namespace)\\n            // highlight-end        \\n\\n            PlayerButton(action: {\\n\\n            }, icon: \\\"play.fill\\\", isBig: true)\\n                        .cornerRadius(4)\\n            // highlight-start\\n            .matchedGeometryEffect(id: \\\"\\\\(track.title)-play\\\" , in: namespace)\\n            // highlight-end\\n\\n            PlayerButton(action: {\\n\\n            }, icon: \\\"goforward.15\\\", isBig: true)\\n            .cornerRadius(4)\\n            // highlight-start\\n            .matchedGeometryEffect(id: \\\"\\\\(track.title)-forward\\\" , in: namespace)\\n            // highlight-end\\n\\n        }\\n    }\\n}\\n\\nstruct ExpandedPlayerComponent_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    @Namespace private static var animation\\n    // highlight-end\\n\\n    static var previews: some View {\\n        Group {\\n            // highlight-start\\n            ExpandedPlayerComponent(track: Track.placeholder, isExpanded: .constant(false), offsetY: .constant(0), namespace: animation)\\n                // highlight-end\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            // highlight-start\\n            ExpandedPlayerComponent(track: Track.placeholder, isExpanded: .constant(false), offsetY: .constant(0), namespace: animation)\\n                // highlight-end\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"This view is getting messy, and desperately needs refactoring. I have done this on purpose  to let you do it  by yourself.  Doing this will actually help you understand the code better when you refactor the view yourself.\"), mdx(\"h2\", null, \"Update FoldedPlayerComponent\"), mdx(\"p\", null, \"Just like the expanded component, we add the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"matchedGeometryEffect\"), \" modifier to views that we want their transitions synchronised with the ones in the expanded component.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson30FoldedPlayerComponent1\\n// $$liquidcoderFilename-FoldedPlayerComponent\\n\\nimport SwiftUI\\n\\nstruct FoldedPlayerComponent: View {\\n\\n    var track: Track\\n\\n    @Binding var isExpanded: Bool\\n    @Binding var offsetY: CGFloat\\n\\n    // highlight-start\\n    var namespace: Namespace.ID\\n    // highlight-end\\n\\n    var body: some View {\\n        HStack {\\n            TrackImageView(track: track)\\n                .cornerRadius(10)\\n                // highlight-start\\n                .matchedGeometryEffect(id: \\\"\\\\(track.title)-image\\\", in: namespace)\\n                // highlight-end\\n                .frame(width: 70, height: 70)\\n\\n            VStack(alignment: .leading, spacing: 10) {\\n\\n                Text(track.title)\\n                    .font(.callout)\\n                    .lineLimit(1)\\n                // highlight-start\\n                                        .matchedGeometryEffect(id: \\\"\\\\(track.title)-progressView\\\" ,\\n                                            in: namespace)\\n                // highlight-end\\n\\n                Text(track.podcastName)\\n                    .font(.footnote)\\n                    .lineLimit(1)\\n                    .foregroundColor(.gray)\\n            // highlight-start\\n            }.matchedGeometryEffect(id: \\\"\\\\(track.title)-titles\\\" ,\\n                                    in: namespace)\\n            // highlight-end\\n\\n            PlayerButton(action: {}, icon: \\\"play.fill\\\", isBig: false)\\n                // highlight-start\\n                .matchedGeometryEffect(id: \\\"\\\\(track.title)-play\\\", in: namespace)\\n                .matchedGeometryEffect(id: \\\"\\\\(track.title)-backward\\\" , in: namespace)\\n                .matchedGeometryEffect(id: \\\"\\\\(track.title)-forward\\\" , in: namespace)\\n                // highlight-end\\n                .frame(maxHeight: .infinity)\\n        }.padding(10)\\n                .onTapGesture {\\n            self.isExpanded = true\\n            self.offsetY = 0\\n        }\\n    }\\n}\\n\\nstruct FoldedPlayerComponent_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    @Namespace private static var namespace\\n    // highlight-end\\n\\n    static var previews: some View {\\n        Group {\\n            // highlight-start\\n            FoldedPlayerComponent(track: Track.placeholder, isExpanded: .constant(false), offsetY: .constant(0), namespace: namespace)\\n                // highlight-end\\n                .previewLayout(.sizeThatFits)\\n                .preferredColorScheme(.dark)\\n            // highlight-start\\n            FoldedPlayerComponent(track: Track.placeholder, isExpanded: .constant(false), offsetY: .constant(0), namespace: namespace)\\n                // highlight-end\\n                .previewLayout(.sizeThatFits)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"Notice how we add 3 \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"matchedGeometryEffect\"), \" modifiers to a single view pointing to 3 different views. This will create an effect where the 3 buttons from the expanded player component transition from this single button.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson30PlayerView5\\n// $$liquidcoderFilename-PlayerView\\n\\nimport SwiftUI\\n\\nstruct PlayerView: View {\\n\\n    @State private var isExpanded = true\\n    @State private var offsetY: CGFloat = 0\\n\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?\\n    @EnvironmentObject private var playerManager: PlayerManager\\n\\n    @Namespace private var animation\\n\\n    var body: some View {\\n        switch playerManager.state {\\n        case .idle(let placeholder):\\n            createContent(track: placeholder)\\n                .redacted(reason: .placeholder)\\n        case .loading:\\n            ProgressView(\\\"Loading...\\\")\\n                .padding()\\n        case .playing(let track):\\n            createContent(track: track)\\n        case .failed(let message):\\n            Text(message)\\n        }\\n    }\\n\\n    func createContent(track: Track) -> some View {\\n        ZStack(alignment: .bottom, content: {\\n            if isExpanded {\\n// highlight-start\\n                ExpandedPlayerComponent(track: track, isExpanded: $isExpanded, offsetY: $offsetY, namespace: animation)\\n// highlight-end\\n            } else {\\n// highlight-start\\n                FoldedPlayerComponent(track: track, isExpanded: $isExpanded, offsetY: $offsetY, namespace: animation )\\n// highlight-end\\n            }\\n        }).background(BlurView())\\n        .cornerRadius(20)\\n        .roundedBorder(radius: 20, color: Color.border)\\n        .padding()\\n        .padding(.bottom, 10)\\n        .animation(.spring(response: 0.55, dampingFraction: 0.9, blendDuration: 0), value: self.isExpanded)\\n    }\\n}\\n\\nstruct PlayerView_Previews: PreviewProvider {\\n    static var previews: some View {\\n        Group {\\n            PlayerView()\\n                .preferredColorScheme(.dark)\\n                .previewLayout(.sizeThatFits)\\n            PlayerView()\\n                .previewLayout(.sizeThatFits)\\n        }.environmentObject(PlayerManager())\\n    }\\n}\\n\")), mdx(\"p\", null, \"Now that we have introduced another property in the 3 views, let's update their respective call site to add the missing namespace parameter.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1617737728/StreamCast/Lesson%2030/iuzphylg5d9theswbtgg.gif\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1617737728/StreamCast/Lesson%2030/iuzphylg5d9theswbtgg.gif\"\n  }))), mdx(\"h2\", null, \"Update MainScreen\"), mdx(\"p\", null, \"We want the player component to be aligned to the right edge of the screen on larger displays, and be centered on narrow ones that's why we create that \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"alignment\"), \" function that returns appropriate \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Alignment\"), \" value based on the horizontal size class\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson30UpdateMainScreen1\\n// $$liquidcoderFilename-MainScreen\\n\\nimport SwiftUI\\n\\nstruct MainScreen: View {\\n\\n    #if os(iOS)\\n    @Environment(\\\\.horizontalSizeClass) private var hSizeClass: UserInterfaceSizeClass?\\n    #endif\\n\\n    var body: some View {\\n        // highlight-start\\n        ZStack(alignment: alignment()) {\\n        // highlight-end\\n            NavigationView {\\n                if hSizeClass == .regular {\\n                   Sidebar()\\n                } else {\\n                    HomeScreen()\\n                }\\n            }\\n                    // highlight-start\\n            PlayerView()\\n        // highlight-end\\n\\n        }.edgesIgnoringSafeArea(.all)\\n    }\\n\\n    // highlight-start\\n    func alignment() -> Alignment {\\n        switch hSizeClass {\\n        case .regular:\\n            return .bottomTrailing\\n        case .compact:\\n            return .bottom\\n        default:\\n            return .bottom\\n        }\\n    }\\n    // highlight-end\\n}\\n\\nstruct MainScreen_Previews: PreviewProvider {\\n\\n    // highlight-start\\n    static let store = Store(environment: AppEnvironment(api: PreviewApiService(), coredata: PreviewCoredataService()))\\n    static let playerManager = PlayerManager()\\n        static let downloadManager = DownloadManager()\\n    // highlight-end\\n\\n    static var previews: some View {\\n        Group {\\n            MainScreen()\\n                .preferredColorScheme(.dark)\\n            MainScreen()\\n            // highlight-start\\n        }.environmentObject(store)\\n        .environmentObject(playerManager)\\n                .environmentObject(downloadManager)\\n        // highlight-end\\n    }\\n}\\n\")), mdx(\"p\", null, \"Last, we attach a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PlayerManager\"), \" object to the content view in order to run the app.\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1617737945/StreamCast/Lesson%2030/gw8rd2pdzjnswa2zlvac.png\",\n    \"alt\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1617737945/StreamCast/Lesson%2030/gw8rd2pdzjnswa2zlvac.png\"\n  }))), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"// $$liquidcoderID-Lesson30UpdateStreamCastApp1\\n// $$liquidcoderFilename-StreamCastApp\\n\\nimport SwiftUI\\n\\n@main\\nstruct StreamCastApp: App {\\n    let persistenceController = PersistenceController.shared\\n    @ObservedObject private var store = Store()\\n    @ObservedObject private var downloadManager = DownloadManager()\\n    // highlight-start\\n    @ObservedObject private var playerManager = PlayerManager()\\n    // highlight-end\\n\\n    var body: some Scene {\\n        WindowGroup {\\n            ContentView()\\n                .environmentObject(store)\\n                .environmentObject(downloadManager)\\n                // highlight-start\\n                .environmentObject(playerManager)\\n                // highlight-end\\n                .environment(\\\\.managedObjectContext, persistenceController.container.viewContext)\\n        }\\n    }\\n}\\n\")), mdx(\"p\", null, \"And this brings this lesson to a close, in the next one we will work on playing the actual episodes. See you then and happy coding!.\"));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#blurview","title":"BlurView"},{"url":"#playerview","title":"PlayerView"},{"url":"#update-expandedplayercomponent","title":"Update ExpandedPlayerComponent"},{"url":"#update-foldedplayercomponent","title":"Update FoldedPlayerComponent"},{"url":"#update-mainscreen","title":"Update MainScreen"}]}}}}]}},"pageContext":{"slug":"streamcast"}},"staticQueryHashes":["1047155515","1278771865","2517639938","2517639938","2537039069","3452127246","3566879862","3891201089","439617394","670651827"]}