<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Apple Developer Academy | UFPE - Medium]]></title>
        <description><![CDATA[Publicações sobre o desenvolvimento de aplicativos iOS, iPadOS, tvOS e WatchOS de mentores, estudantes e alumni da Apple Developer Academy | UFPE - Medium]]></description>
        <link>https://medium.com/appledeveloperacademy-ufpe?source=rss----b27283248e96---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Apple Developer Academy | UFPE - Medium</title>
            <link>https://medium.com/appledeveloperacademy-ufpe?source=rss----b27283248e96---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 16:15:30 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/appledeveloperacademy-ufpe" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Como usar efetivamente o Jira]]></title>
            <link>https://medium.com/appledeveloperacademy-ufpe/como-usar-efetivamente-o-jira-f37911fcfded?source=rss----b27283248e96---4</link>
            <guid isPermaLink="false">https://medium.com/p/f37911fcfded</guid>
            <category><![CDATA[jira]]></category>
            <category><![CDATA[project-management]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Michel Silva]]></dc:creator>
            <pubDate>Tue, 27 Oct 2026 15:22:52 GMT</pubDate>
            <atom:updated>2026-10-27T15:22:51.009Z</atom:updated>
            <content:encoded><![CDATA[<h4>E por quê?</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9UbYjM7vmnte4xNZuX9mLQ.jpeg" /><figcaption>Sprint — Rodolfo Cora</figcaption></figure><p>Seja na área da tecnologia, design, desenvolvimento de produto, gestão ou até mesmo em projetos da universidade, quando falamos de trabalho em equipe a <strong>gestão de tempo</strong>, qualidade de <strong>entregas</strong> e <strong>evolução</strong> <strong>contínua</strong> do projeto são questões importantes para um bom resultado final. É necessário <strong>acompanhar</strong> o trabalho dos seu time, combinar <strong>prazos</strong> e realizar entregas progressivas que <strong>agreguem valor</strong> ao projeto. Precisamos utilizar estratégias de metodologias ágeis como o S<em>crum</em> e o K<em>anban</em>.</p><p>De forma objetiva,</p><blockquote><strong>Scrum</strong> é uma <strong>metodologia ágil</strong> usada para gerenciar projetos de forma colaborativa, incremental e adaptável e principalmente em cenários de maior incerteza, permitindo uma resposta rápida às mudanças e uma entrega mais flexível. […] Ela preza por utilizar ciclos curtos de trabalho, chamadas de <strong>Sprints</strong> e que duram entre 1 e 4 semanas. Tem pequenas entregas ao longo do projeto, reuniões curtas diariamente chamadas de <strong>Daily</strong>, para acompanhar o andamento do projeto. E a cada Sprint, ocorre uma revisão do que foi produzido (incremento no produto) que chamamos de <strong>Review </strong>e um momento para discutir o que pode ser melhorado (processo) nas próximas Sprints que chamamos de<strong> Retrospective</strong>.</blockquote><p><em>Veja mais sobre </em><a href="https://medium.com/appledeveloperacademy-ufpe/metodologia-%C3%A1geis-na-%C3%A1rea-de-tecnologia-0dac18d07b4f?sharedUserId=micheldeoliveirasilva"><em>metodologias ágeis</em></a><em> no texto de </em><a href="https://medium.com/u/679f967453fb"><em>Camila Xavier</em></a><em>.</em></p><p>Já o Kanban,</p><blockquote>O Kanban é um método visual de gerenciamento de fluxo de trabalho que permite às equipes otimizar e melhorar sempre a entrega de trabalho ao <strong>visualizar tarefas, limitar o trabalho em andamento e focar na eficiência do fluxo.</strong> Em um sistema Kanban, as equipes <strong>visualizam as tarefas em um quadro</strong>, movem o trabalho por estágios definidos e usam limites claros para evitar que muito trabalho se acumule de uma vez. Um dos principais <a href="https://www.atlassian.com/br/agile/project-management/kanban-principles">princípios do Kanban</a> é tornar os fluxos de trabalho mais fáceis de visualizar, gerenciar e aprimorar.</blockquote><p><em>Veja mais sobre o Kanban nos textos da </em><a href="https://www.atlassian.com/br/agile/kanban"><em>Atlassian</em></a><em>, empresa responsável pela plataforma Jira.</em></p><h3>E por que o Jira?</h3><p>A plataforma unifica a metodologia do <em>Scrum</em> e a visualização dinâmica do <em>Kanban</em>. Mesmo dentro dos planos gratuitos, é possível realizar a organização de um <strong>B<em>acklog</em></strong><em>, </em>lista de <em>tasks</em> para serem entregues, e a divisão dessas por <strong><em>Sprints</em></strong><em> </em>com tempo de duração determinado pela equipe.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_-yd_de_K5Cpwqt7rPaqxQ.png" /><figcaption>Página de Backlog do Jira</figcaption></figure><p>Dentro da página do <strong>b<em>acklog</em></strong>, o time consegue visualizar em uma única tela as <strong><em>tasks</em></strong> em formato de lista. Em cada uma das tasks é possível escolher um título, alterar o progresso, apontar um <strong>prazo de entrega</strong> desejado, determinar uma <strong>pessoa responsável</strong> pela atividade. Além de conseguir facilmente reorganizar o trabalho ao longo do processo.</p><p>Para desenvolvimento de software, durante o momento da <em>sprint planning</em>, as sprints devem ser construídas a partir das &quot;<strong><em>user stories</em></strong>&quot;. A user story é uma ação completa que o seu usuário precisa realizar: &quot;<a href="https://www.brunafonseca.pro.br/post/o-que-%C3%A9-uma-user-story"><em>Como [usuário], quero [ação] para que [objetivo]”</em></a>, ou seja, ao fim de um sprint o time deve entregar um pedaço de um fluxo para o projeto.</p><p>Para auxiliar nas discussões do time, durante o planejamento, de quais <em>tasks</em> devem fazer parte de cada sprint, utiliza-se os <a href="https://ones.com/blog/story-points-vs-original-estimate-jira-sprint-planning/"><em>story points</em></a><em> </em>para estimar <strong>o esforço, </strong>uma unidade de medida criada a partir da sequência de fibonacci (1, 2, 3, 5, 8, 13). Essa medida tem como objetivo distanciar a discussão de unidades exatas, como horas que podem gerar divergências, e levar à um campo mais abstrato, que pode levar em consideração: dificuldade técnica, tempo de estudo, conhecimento prévio, tempo disponível para o trabalho e a comparação entre <em>tasks</em>, evitando repetir a enumeração em uma mesma <em>sprint</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*P89CczEsbvppK-5r" /><figcaption>Fibonacci sequence</figcaption></figure><p>Em projetos que estão iniciando do zero, escolher bem as atividades e montar user stories realisticamente é muito importante, pois além de aumentar o sentimento de satisfação da equipe ao conseguir visualizar pedaços do seu projeto saindo do papel, proporciona entregas progressivas aos <em>stakeholders</em>, financiadores ou líderes de outras áreas.</p><p>Assim como também, facilitar correções e revisões por fluxo de usuário. Sendo mais fácil de realizar alterações e mudanças de rumo durante o processo de construção, ao invés de apenas ao final do projeto, economizando tempo e dinheiro.</p><h3>Como criar um projeto no Jira pela primeira vez?</h3><ol><li>Acesse o Jira e faça o seu login ou crie um nova conta.</li><li>Crie um site onde seus múltiplos espaços poderão ser criados</li><li>De um nome para o seu espaço (você pode criar por equipe, por projeto)</li><li>Organize o Kanban como preferir</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*K8Z2PfcLu2RGKkHS--2oAw.png" /><figcaption>Capturas de Telas do Autor</figcaption></figure><p>5. Por último, convide seus <em>teammates</em> para o espaço.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*r7wLnbtfkj0of2Zd_3BfBQ.png" /></figure><h3>E isso resolve tudo?</h3><p>Não. O Jira não vai eliminar todos os problemas do seu time. Mas, não usá-lo pode gerá-los.</p><p>Um projeto sem organização e visualização do processo pode ter dificuldades de identificação de gargalos técnicos, aumento do tempo necessário para entregas efetivas ou ser um gerador de estresse ao time com tarefas complexas que poderiam ter sido melhor administradas, pensadas e que talvez não sejam prioritárias para a entrega final.</p><p>Mas, só utilizar a ferramenta, dividindo tarefas e esperando o melhor, sem o uso efetivo do <em>Scrum</em>, das <em>Dailys</em> e do acompanhamento não gerará resultados efetivas, mas sim um quadro com tarefas que ninguém finaliza, testa ou conversa sobre.</p><p><em>Caso deseje aprender mais sobre o Scrum, cheque o último texto de </em><a href="https://medium.com/@deblemos/como-adotar-pr%C3%A1ticas-do-scrum-no-seu-trabalho-em-equipe-f27a226d34e0"><em>Débora Lemos</em></a><em>.</em></p><h3>Referências e leituras recomendadas</h3><p>ATLASSIAN. <strong>O que é o Kanban?</strong>. Atlassian, 2026. Disponível em: <a href="https://www.atlassian.com/br/agile/kanban">https://www.atlassian.com/br/agile/kanban</a>. Acesso em: 20 out. 2026.</p><p>FONSECA, Bruna. <strong>O que é uma User Story?</strong> Bruna Fonseca, 10 jun. 2021. Disponível em: <a href="https://www.brunafonseca.pro.br/post/o-que-%C3%A9-uma-user-story">https://www.brunafonseca.pro.br/post/o-que-é-uma-user-story</a>. Acesso em: 20 out. 2026.</p><p>ONES. <strong>Story Points vs Original Estimate in Jira</strong>: which metric drives better sprint planning? ONES.com Blog, 27 fev. 2026. Disponível em: <a href="https://ones.com/blog/story-points-vs-original-estimate-jira-sprint-planning/">https://ones.com/blog/story-points-vs-original-estimate-jira-sprint-planning/</a>. Acesso em: 20 out. 2026.</p><p>XAVIER, Camila. <strong>Metodologias ágeis na área de tecnologia!</strong>: importância do gerente de projetos e o uso do Scrum no processo de desenvolvimento de um produto. Medium: Apple Developer Academy | UFPE, 14 maio 2025. Disponível em: <a href="https://medium.com/appledeveloperacademy-ufpe/metodologia-%C3%A1geis-na-%C3%A1rea-de-tecnologia-0dac18d07b4f">https://medium.com/appledeveloperacademy-ufpe/metodologia-ágeis-na-área-de-tecnologia-0dac18d07b4f</a>. Acesso em: 20 out. 2026.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f37911fcfded" width="1" height="1" alt=""><hr><p><a href="https://medium.com/appledeveloperacademy-ufpe/como-usar-efetivamente-o-jira-f37911fcfded">Como usar efetivamente o Jira</a> was originally published in <a href="https://medium.com/appledeveloperacademy-ufpe">Apple Developer Academy | UFPE</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Tomada de decisão: como guiar um grupo para convergir ideias]]></title>
            <link>https://medium.com/appledeveloperacademy-ufpe/tomada-de-decis%C3%A3o-como-guiar-um-grupo-para-convergir-ideias-3fb83b3b47ff?source=rss----b27283248e96---4</link>
            <guid isPermaLink="false">https://medium.com/p/3fb83b3b47ff</guid>
            <category><![CDATA[técnica]]></category>
            <category><![CDATA[tomada-de-decisão]]></category>
            <category><![CDATA[trabalho-em-grupo]]></category>
            <category><![CDATA[metodologia]]></category>
            <category><![CDATA[liderança]]></category>
            <dc:creator><![CDATA[Letícia Staudinger]]></dc:creator>
            <pubDate>Tue, 27 Oct 2026 15:16:56 GMT</pubDate>
            <atom:updated>2026-10-27T19:36:09.576Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*v1vhkJkdvzTHVUOr" /><figcaption>Liderando um grupo. Fonte: <a href="https://unsplash.com/pt-br/fotografias/um-grupo-de-pessoas-sentadas-ao-redor-de-uma-mesa-com-laptops-uZyE3w7khzw">UK Black Tech, Unsplash</a>.</figcaption></figure><p>Tomar decisões é um aspecto intrínseco a processos criativos. No trabalho em grupo, é importante que esse ato seja realizado de forma organizada, empática e guiada, uma vez que o nível de participação dos integrantes na jornada tem um alto impacto no engajamento, satisfação e sentimento de dono em relação ao que está sendo desenvolvido.</p><p>Neste artigo, serão abordados aspectos relevantes de preparação e técnicas para tomada de decisão em grupo. As reflexões sobre o tema surgiram a partir da minha experiência criando e desenvolvendo o aplicativo <a href="https://apps.apple.com/br/app/pupila/id6813093449"><strong>Pupila</strong></a>, disponível para iOS, com meus colegas da <a href="https://medium.com/appledeveloperacademy-ufpe">Apple Developer Academy — UFPE</a>. Desse modo, a minha motivação é colaborar para um conhecimento compartilhado e mais amplo sobre maneiras de tornar esses momentos mais fluidos e direcionados.</p><h4>Metodologia</h4><p>Em projetos mais longos, é comum o uso de metodologias para guiar o trabalho da equipe. Nesse viés, durante o desenvolvimento do <a href="https://apps.apple.com/br/app/pupila/id6813093449"><strong>Pupila</strong></a>, utilizamos o <a href="https://www.challengebasedlearning.org/"><strong>Challenge Based Learning</strong></a><strong> (CBL)</strong>, metodologia que tem como base o aprendizado constante e imersão no universo do problema. De maneira geral, aprende-se enquanto cria uma solução e resolve o problema em questão. O CBL é dividido em três fases principais, interligadas entre si:</p><ul><li><strong>Engage (Engajar):</strong> define-se a grande ideia, um tema amplo. Ela fornece propósito e guia o processo. Durante essa etapa, muitos questionamentos são levantados e um desafio mais específico é definido;</li><li><strong>Investigate (Investigar):</strong> diante do desafio, são realizadas perguntas/reflexões, as quais são respondidas através de estudo e aprendizado aprofundados. Nesse momento há grande uso do pensamento crítico. Por fim, escolhe-se uma solução e registra-se as sínteses e evidências que baseiam a escolha;</li><li><strong>Act (Agir):</strong> desenvolve-se a solução real a partir das decisões anteriores. É importante ser adaptável ao que o público alvo necessita, assim como entender os possíveis erros e acertos durante o caminho para reavaliar o planejamento, se necessário.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/proxy/0*uhbb45GOEpqYQHzG.png" /><figcaption>Framework CBL. Fonte: <a href="https://www.challengebasedlearning.org/framework/">https://www.challengebasedlearning.org/framework/</a></figcaption></figure><p>Diante do exposto, nota-se que o CBL traz à tona questões interessantes, muito relacionadas à tomada de decisão: flexibilidade e pensamento crítico.</p><blockquote>É importante, durante o processo, experimentar o entendimento de outras sugestões e opiniões pela ótica do outro, estando flexível a mudanças de contexto e/ou viés.</blockquote><p>Além disso, <strong>é essencial pensar criticamente acerca da problemática e das ideias sugeridas, de modo que as decisões sejam fundamentadas, coerentemente, em evidências</strong>.</p><p>Essas questões, quando praticadas e alinhadas em grupo, criam um ambiente seguro em que as pessoas se sentem confortáveis para compartilhar opiniões, garantindo que o contexto das proposições tenha um fluxo mais natural.</p><h4>Preparação</h4><p>Imagine um processo mais complexo de ideação, como a construção de um aplicativo que resolve um problema real. Nele, é necessário identificar a problemática, estudar sobre a área focal, entender o público alvo, dialogar com essas pessoas, realizar pesquisas, entre outras ações. A partir disso, todos os dados coletados, as informações extraídas e as conclusões da equipe devem ser registradas e alinhadas constantemente.</p><p>O registro, por sua vez, pode acontecer de diversas maneiras e ter diferentes formatos. No Pupila, utilizamos uma <a href="https://www.nngroup.com/articles/csd-matrix/"><strong>Matriz CSD</strong></a> (Matriz de Certezas, Suposições e Dúvidas) como um &quot;raio X&quot; do grupo, por exemplo. Ela foi atualizada a cada ciclo: algumas suposições viravam certezas, novas dúvidas surgiam… Então ela sempre refletia a visão e posição do grupo no momento, funcionando como um registro “vivo”.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*fsuq_3m_MVJarOn3.jpg" /><figcaption>Matriz CSD. Fonte: <a href="https://www.nngroup.com/articles/csd-matrix/">https://www.nngroup.com/articles/csd-matrix/</a></figcaption></figure><p>Além da matriz, as construções gerais eram guardadas em outros formatos, tal como documentos escritos, formulários e imagens. Elas eram inseridas em plataformas como o <a href="https://miro.com/pt/">Miro</a> e o <a href="https://www.figma.com/">Figma</a>, que serviam como nosso acervo de elaborações, decisões, sínteses etc. Quando necessitávamos consultar ou realinhar algum aspecto, partíamos para esses locais.</p><p>Outrossim, <strong>também é interessante realizar um alinhamento de expectativas da equipe considerando o processo de tomada de decisão</strong>. Nesse cenário, além da flexibilidade e do pensamento crítico, outros pilares cruciais são:</p><ul><li><strong>Embasar suas sugestões e justificativas em dados e/ou evidências reais.</strong> Para isso, usam-se os registros mencionados previamente neste tópico;</li><li><strong>Ter uma escuta ativa.</strong> Ouvindo com atenção e considerando o que está sendo dito por outras pessoas;</li><li><strong>Definir, com clareza, o objetivo final</strong> da convergência.</li></ul><h4>Etapas e técnicas</h4><p>Entende-se que, para decidir algo, é necessário que existam opções. Desse modo, considerando que a equipe já realizou momentos de geração de ideias previamente, como o <a href="https://www.atlassian.com/br/work-management/project-collaboration/brainstorming">Brainstorming</a>, sugiro as seguintes etapas e técnicas, baseadas em estudos e experiências passadas. Contudo, vale ressaltar que não existe uma regra absoluta: <strong>tudo depende da dinâmica e necessidades do grupo</strong>.</p><p><strong>1. Organizar e agrupar</strong></p><ul><li>Juntar todas as ideias em um único local, seja físico ou online;</li><li>Eliminar ideias duplicadas;</li><li>Agrupar ideias semelhantes em categorias, por exemplo, com um <a href="https://asq.org/quality-resources/affinity">Diagrama de Afinidades</a>.</li></ul><p><strong>2. Discutir e esclarecer</strong></p><ul><li>Ler as ideias em grupo;</li><li>Abrir espaço para questionamentos rápidos para seus autores.</li></ul><p><strong>3. Filtrar e selecionar</strong></p><ul><li>Relembrar qual o objetivo final da decisão;</li><li>Ouvir opiniões organizadamente (por categoria, por ordem de disposição das pessoas no local etc.);</li><li>Selecionar, em grupo, as ideias que fazem mais sentido (<a href="https://www.nngroup.com/articles/dot-voting/">Votação por Pontos</a>, <a href="https://www.fm2s.com.br/blog/matriz-esforco-x-impacto">Matriz de Impacto x Esforço</a>). Nesse momento, é válido revisitar registros, acordos firmados e decisões anteriores.</li></ul><p><strong>4. Refinar e concluir</strong></p><ul><li>Estimular o grupo a construir em cima das ideias selecionadas na etapa 3, combinando-as;</li><li>Definir a ideia final (a partir de uma técnica de seleção, como as citadas previamente);</li><li>Consultar se o time tem alguma objeção (<a href="https://www.sociocracyforall.org/pt-br/tomada-de-decisao-por-consentimento/">Tomada de Decisão por Consentimento</a>) ou se a ideia fere alguma limitação ou acordo previamente estabelecido;</li><li>Registrar decisão.</li></ul><h4>Considerações finais</h4><p>Em suma, guiar um grupo para a convergência de ideias não é um processo rígido.</p><p>Embora existam diversas técnicas e metodologias disponíveis para facilitar essa jornada, <strong>a escolha ideal sempre dependerá do perfil da equipe com a qual você está trabalhando e do contexto específico do projeto</strong>. O que faz mais sentido e funciona perfeitamente para um time pode não ser a melhor abordagem para outro.</p><p>Portanto, o mais importante é manter a flexibilidade, a escuta ativa e adaptar essas ferramentas à realidade do seu grupo, garantindo que a tomada de decisão seja um momento colaborativo, engajador e eficaz.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3fb83b3b47ff" width="1" height="1" alt=""><hr><p><a href="https://medium.com/appledeveloperacademy-ufpe/tomada-de-decis%C3%A3o-como-guiar-um-grupo-para-convergir-ideias-3fb83b3b47ff">Tomada de decisão: como guiar um grupo para convergir ideias</a> was originally published in <a href="https://medium.com/appledeveloperacademy-ufpe">Apple Developer Academy | UFPE</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Como selecionar e reproduzir vídeos da galeria no SwiftUI com PhotosPicker e AVKit]]></title>
            <link>https://medium.com/appledeveloperacademy-ufpe/como-selecionar-e-reproduzir-v%C3%ADdeos-da-galeria-no-swiftui-com-photospicker-e-avkit-f5f3666ab0e2?source=rss----b27283248e96---4</link>
            <guid isPermaLink="false">https://medium.com/p/f5f3666ab0e2</guid>
            <category><![CDATA[avkit]]></category>
            <category><![CDATA[coding]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[avfoundation]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[clarissafhonorio]]></dc:creator>
            <pubDate>Thu, 22 Oct 2026 14:23:16 GMT</pubDate>
            <atom:updated>2026-10-22T14:23:15.033Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*spRD6Z7vYKa-Jwh47Y_11Q.jpeg" /></figure><p>Já aplicou o PhotosPicker em algum projeto e tem dúvida de como possibilitar que o usuário selecione vídeos da fototeca e os reproduza em alguma View? A solução está no uso da AVKit.</p><h3>O que é AVKit?</h3><p>AVKit é um framework da Apple que fornece as interfaces necessárias para integrar mídia com o hardware e software de um dispositivo, fornecendo o reprodutor de vídeo padrão do sistema. Ele se baseia no AVFoundation, responsável por todo o trabalho de baixo nível por trás da integração de áudio e vídeo. Com o AVKit, o seu projeto pode reproduzir mídia em tela cheia, embutida em linha ou em uma janela flutuante Picture in Picture (PiP).</p><p>Abaixo estão os frameworks relevantes:</p><pre>import SwiftUI<br>import PhotosUI<br>import AVKit</pre><h3>Quais são os componentes necessários?</h3><ol><li>PhotosPickerItem — referência retornada pelo PhotosPicker que indica o vídeo selecionado</li><li>AVPlayer — gerencia a reprodução do vídeo</li><li>VideoPlayer — visualização que exibe o conteúdo do AVPlayer</li></ol><h3>Como implementar?</h3><h4>1. Configurar as variáveis necessárias</h4><pre>    var selectedPickerItem: PhotosPickerItem? = nil<br>    var selectedVideoData: Data? = nil // dados brutos do vídeo<br>    var isLoading: Bool = false //flag pra indicar se está carregando<br>    var player: AVPlayer? = nil</pre><h4>2. Criar uma URL temporária</h4><p>O AVPlayer não reproduz vídeos diretamente a partir de um objeto Data: ele precisa de uma URL. Para contornar esse obstáculo, vamos retornar uma URL temporária, já que, por questões de privacidade, não podemos utilizar a original do PhotosPicker.</p><pre>    private func criarURLTemporaria(com data: Data) -&gt; URL? {<br>        let tempDirectory = FileManager.default.temporaryDirectory //acessa a pasta temporária do app (tmp/)<br>        let tempURL = tempDirectory.appendingPathComponent(UUID().uuidString).appendingPathExtension(&quot;mp4&quot;) //cria um nome único<br>        do {<br>            try data.write(to: tempURL) //grava o vídeo nesse url<br>            return tempURL //retorna a URL criada<br>        } catch {<br>            print(&quot;Erro ao criar arquivo temporário de vídeo: \(error.localizedDescription)&quot;)<br>            return nil<br>        }<br>    }</pre><h4>3. Processar o vídeo selecionado</h4><p>Vamos implementar uma função processarVideoSelecionado, com o intuito de transformar o selectedPickerItem em um vídeo reproduzível.</p><p>Esta função cria uma Task, a qual roda na thread principal, mas permite que o carregamento aconteça de forma assíncrona (pelo uso do &quot;await&quot;), liberando a interface enquanto o vídeo é carregado e impedindo que o app fique travado.</p><p>A Task aguarda o loadTransferable interpretar o vídeo da fototeca como bytes (Data) e os guarda para depois transformá-los em uma URL, a partir da qual será criado o AVPlayer.</p><pre>    @MainActor<br>    func processarVideoSelecionado() {<br>        guard let item = selectedPickerItem else { return } // algum video selecionado?<br>        <br>        isLoading = true<br>        <br>        Task {<br>            if let data = try? await item.loadTransferable(type: Data.self) {<br>                // convertendo o video em bytes para um objeto Data<br>                self.selectedVideoData = data<br>                <br>                if let tempURL = self.criarURLTemporaria(com: data) {<br>                    self.player = AVPlayer(url: tempURL)<br>                }<br>            }<br>            self.isLoading = false<br>        }<br>    }</pre><p>Observações relevantes:</p><ul><li>o @MainActor garante que ela rodará na thread principal junto à interface. É importante adicionar esse atributo, pois a função irá alterar variáveis que a View observa (isLoading, player).</li><li>o loadTransferable(type: Data.self) não é indicado para arquivos muito grandes.</li></ul><h4>4. PhotosPicker</h4><p>Deve-se adicionar um filtro ao PhotosPicker para listar apenas os vídeos do usuário. Depois, deve-se implementar um preview usando AVPlayer</p><pre>PhotosPicker(<br>    selection: $selectedPickerItem,<br>    matching: .videos, //filtra so videos<br>    photoLibrary: .shared()<br>) {<br>    //preview do vídeo usando AVPlayer<br>    if let player {<br>        VideoPlayer(player: player)<br>    }<br>    else if isLoading {<br>        ProgressView()<br>    }<br>    else {<br>        Text(&quot;Adicionar Vídeo&quot;)<br>    }<br>}<br><br>.onChange(of: selectedPickerItem) {<br>    processarVideoSelecionado()<br>} //quando selecionar entra no modo processando<br>.onDisappear {<br>    cancelar() //função que limpa a memória ao fechar<br>}</pre><p>Tendo feito isso, resta apenas adicionar um botão que salve os dados do vídeo no banco de dados do projeto. No exemplo abaixo, criamos uma nova instância da classe &quot;Video&quot; com os dados do vídeo como atributo (selectedVideoData) e nós a inserimos no ModelContext (classe do SwiftData):</p><pre>let novoVideo = Video(videoData: selectedVideoData)<br>context.insert(novoVideo)</pre><h4>5. Prévia do vídeo</h4><p>Agora, em qualquer tela que você desejar incluir uma prévia desse vídeo que o usuário adicionou ao projeto, basta chamar o componente VideoPlayer em uma View.</p><pre>if let player {<br>    VideoPlayer(player: player)<br>}<br>else {<br>    Text(&quot;Não há vídeos&quot;)<br>}</pre><h3>Conclusão</h3><p>Com esses passos base, o seu app já permitirá que o usuário escolha vídeos da biblioteca de fotos, veja uma prévia na hora e guarde o arquivo para assistir depois.</p><p>Para além disso, a AVKit também tem muito mais a oferecer (controles personalizados, Picture in Picture, legendas e faixas de áudio, entre outros), então vale a pena explorar a documentação da Apple.</p><p>Espero que esse tutorial tenha lhe ajudado a dar os seus primeiros passos com a AVKit! Se tiver quaisquer dúvidas ou sugestões, sinta-se à vontade para deixá-las nos comentários.</p><h3>Referências</h3><ul><li><a href="https://developer.apple.com/documentation/avkit">AVKit | Apple Developer Documentation</a></li><li><a href="https://developer.apple.com/br/documentation/avkit/videoplayer">VideoPlayer | Apple Developer Documentation</a></li><li><a href="https://developer.apple.com/documentation/avfoundation/avplayer">AVPlayer | Apple Developer Documentation</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f5f3666ab0e2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/appledeveloperacademy-ufpe/como-selecionar-e-reproduzir-v%C3%ADdeos-da-galeria-no-swiftui-com-photospicker-e-avkit-f5f3666ab0e2">Como selecionar e reproduzir vídeos da galeria no SwiftUI com PhotosPicker e AVKit</a> was originally published in <a href="https://medium.com/appledeveloperacademy-ufpe">Apple Developer Academy | UFPE</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Navegação profunda em SwiftUI: entenda como utilizar NavigationPath para lidar com fluxos de telas…]]></title>
            <link>https://medium.com/appledeveloperacademy-ufpe/navega%C3%A7%C3%A3o-profunda-em-swiftui-entenda-como-utilizar-navigationpath-para-lidar-com-fluxos-de-telas-ab6dffb2eed0?source=rss----b27283248e96---4</link>
            <guid isPermaLink="false">https://medium.com/p/ab6dffb2eed0</guid>
            <category><![CDATA[navigation]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[swiftui]]></category>
            <dc:creator><![CDATA[Elena Pimentel]]></dc:creator>
            <pubDate>Thu, 22 Oct 2026 13:04:41 GMT</pubDate>
            <atom:updated>2026-10-22T13:04:40.122Z</atom:updated>
            <content:encoded><![CDATA[<h3>Navegação profunda em SwiftUI: entenda como utilizar NavigationPath para lidar com fluxos de telas complexos</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*FTyhE_9SVtQRqBwC.jpg" /></figure><p>É comum que pessoas iniciantes no desenvolvimento em SwiftUI sustentem a navegação do aplicativo usando somente NavigationStack, normalmente localizadas na ContentView ou outros arquivos raiz, associadas com botões e NavigationLink responsáveis por criar a ligação entre telas. Para projetos de pequeno escopo, com fluxos majoritariamente lineares, esta solução é funcional e resolve o problema. No entanto, quando passamos a lidar com padrões de navegação mais complexos, onde existem várias telas associadas a um único fluxo, precisamos de uma abordagem mais robusta, que permita maior controle da transição entre diferentes View&#39;s. Para esclarecer essas limitações, imagine as seguintes situações:</p><ul><li>O usuário precisa ser direcionado para uma tela específica assim que conclui um preenchimento de dados;</li><li>Queremos retornar para uma tela inicial assim que o usuário finaliza um outro fluxo (por exemplo, após seguir por várias telas para criar um postagem, retornar para a tela do feed após a publicação ser concluída).</li></ul><p>Essas situações exemplificam dois conceitos importantes quando tratamos de navegação:</p><blockquote><strong><em>Navegação Programática:</em></strong><em> permite a navegação “automática” através de gatilhos de código, sem precisar necessariamente de um clique em algum link.</em></blockquote><blockquote><strong><em>Navegação Profunda:</em></strong><em> quando existem vários níveis de telas empilhadas em um mesmo fluxo, e buscamos retornar facilmente à raiz ou acessar pontos específicos dessa estrutura.</em></blockquote><p>Com isso, fica claro que, quanto maior a complexidade do aplicativo em termos de quantas telas percorremos para concluir uma ação, mais difícil se torna gerir esse fluxo e permitir o retorno com facilidade. E é aí que entra o NavigationPath.</p><h3>Entendendo a pilha (stack)</h3><p>Para entender o NavigationPath, precisamos lembrar o conceito de <strong>pilha</strong> (<em>stack</em>): uma estrutura onde o último elemento a entrar é o primeiro a sair (LIFO: <em>Last In, First Out</em>). Como o nome intui, ela funciona como uma pilha de pratos: só conseguimos adicionar ou retirar elementos no topo.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*XJw_uPL57TfetN0y" /><figcaption>Operaçãoes em uma pilha (stack). Fonte: <a href="https://unstop.com/blog/stack-in-data-structure">Unstop</a>.</figcaption></figure><ul><li><strong>Empilhar (push)</strong>: abrir uma nova tela coloca-a no topo da pilha.</li><li><strong>Desempilhar (pop)</strong>: voltar remove a tela do topo.</li><li><strong>Raiz</strong>: a tela na base, que nunca sai da pilha.</li></ul><h3>Como funciona o NavigationPath?</h3><p>NavigationPath é um tipo introduzido no iOS 16 (junto com NavigationStack) que representa uma <strong>pilha de navegação com tipos apagados</strong> (<em>type-erased</em>). Ele guarda os valores que foram “empurrados” (<em>pushed</em>) na navegação, permitindo que você controle a pilha de forma programática: adicionar telas, voltar, voltar à raiz, salvar e restaurar o estado.</p><p>O grande ponto de NavigationPath é que a navegação passa a ser <strong>orientada a dados</strong>: você não navega para “views”, e sim para <strong>valores</strong>, e o SwiftUI decide qual view mostrar para cada tipo de valor. Um detalhe importante: todo valor empilhado precisa ser Hashable.</p><p>Na prática, NavigationPath é responsável apenas por armazenar uma lista dos valores que estão sendo empilhados, agora que a navegação acontece através da passagem destes valores. Para que o fluxo funcione adequadamente, ele atua em conjunto com outros elementos:</p><ul><li><strong>NavigationStack(path:)</strong> exibe a pilha e a mantém sincronizada com o path.</li><li><strong>.navigationDestination(for:)</strong> diz qual tela corresponde a cada tipo de valor.</li></ul><pre>struct ContentView: View {<br>    @State private var path = NavigationPath()<br><br>    var body: some View {<br>        NavigationStack(path: $path) {<br>            Button(&quot;Ver receita&quot;) {<br>                path.append(Receita(id: 1, titulo: &quot;Moqueca de palmito&quot;))<br>            }<br>            .navigationDestination(for: Receita.self) { receita in<br>                ReceitaDetalheView(receita: receita)<br>            }<br>        }<br>    }<br>}<br><br>struct Receita: Hashable {<br>    let id: Int<br>    let titulo: String<br>}</pre><p>Neste trecho de código, o path é a variável observada que inicializa um NavigationPath. Dentro do body, criamos a pilha com NavigationStack(path: $path), passando nossa variável criada anteriormente. Ao clicar no botão, ocorre a ação de adicionar um valor (a receita) ao path, que funciona como o <strong>push</strong> na pilha. O carregamento da View correspondente ao valor só é tratado na .navigationDestination que recebe o tipo de valor de Receita.</p><h4>Operações básicas</h4><p>Como visto anteriormente, a operação em uma pilha permite adicionar e remover valores do topo, ou esvaziá-la completamente. Elas atuam em cima de uma variável que inicializou o NavigationPath, que aqui chamaremos de path. Abaixo são demonstradas as operações possíveis.</p><pre>path.append(valor)        // push: empilha uma tela<br>path.removeLast()         // pop: volta uma tela<br>path.removeLast(2)        // volta duas telas<br>path.count                // quantas telas estão empilhadas<br>path = NavigationPath()   // esvazia tudo: volta à raiz</pre><h3>Aplicação prática para navegação profunda</h3><p>Retornando à questão inicial, ainda precisamos entender como esses tipos de dados e manipulações servem para solucionar o problema da <strong>navegação profunda</strong>. A fim de explicar sua aplicação prática, considere o seguinte contexto: temos uma tela de <strong>Home</strong> com botões que direcionam para fluxos diferentes, a saber: publicar uma receita, publicar um evento ou acessar um perfil.</p><p>Começamos criando os possíveis valores do caminho através de um enum hashable, para permitir que esses valores sejam empilhados. Ainda não estamos tratando das views destes valores, e sim apenas declarando os fluxos de navegação possíveis.</p><pre>enum Rota: Hashable {<br>  case novaReceita<br>  case novoEvento<br>  case perfil(usuario: String)<br>}</pre><p>Depois, um <strong>único lugar</strong> decide qual view corresponde a cada rota:</p><pre>struct HomeView: View {<br>    <br>    @State private var path = NavigationPath()<br>    <br>    var body: some View {<br>        NavigationStack(path: $path) {<br>            VStack(spacing: 20) {<br>                <br>                Button(&quot;Publicar nova receita&quot;) {<br>                    path.append(Rota.novaReceita)<br>                }<br>                <br>                Button(&quot;Publicar novo evento&quot;) {<br>                    path.append(Rota.novoEvento)<br>                }<br>                <br>                Button(&quot;Visualizar perfil&quot;) {<br>                    path.append(Rota.perfil(usuario: &quot;Fulaninho&quot;))<br>                }<br>            }<br>            .navigationDestination(for: Rota.self) { rota in<br>                destino(para: rota)<br>            }<br>        }<br>    }<br>    <br>    // unifica a gestão da rota de destino com um único switch<br>    @ViewBuilder<br>    private func destino(para rota: Rota) -&gt; some View {<br>        switch rota {<br>        case .novaReceita:<br>            CriarReceitaFlowView(path: $path)<br>        case .novoEvento:<br>            CriarEventoFlowView(path: $path)<br>        case .perfil(let usuario):<br>            PerfilView(usuario: usuario)<br>        }<br>    }<br>}</pre><p>Cada view filha fica responsável por gerenciar seu próprio fluxo na pilha e, ao final, pode retornar para a tela inicial:</p><pre>struct PublicarReceitaView: View {<br>    @Binding var path: NavigationPath<br><br>var body: some View {<br>        VStack(spacing: 24) {<br>            Text(&quot;Tudo certo com sua publicação?&quot;)<br>            Button(&quot;Publicar&quot;) {<br>                // envia o post<br>                path = NavigationPath()   // volta direto para a Home<br>            }<br>        }<br>        .navigationTitle(&quot;Publicar receita&quot;)<br>    }<br>}</pre><h4>Observações</h4><ul><li>Um erro comum é adicionar novas <strong>NavigationStacks</strong> nas views filhas. Isso <strong>quebra o funcionamento</strong> pretendido do path, pois a view passa a empilhar seus valores nessa nova pilha e perde o acesso à anterior que a chamou.</li><li>Aqui, estamos chamando FlowViews como views genéricas que direcionam para outras telas de cada fluxo. A <strong>PublicarReceitaView</strong> representa uma última tela desse fluxo, que permite voltar à Home.</li></ul><h3>Uso associado com arquitetura MVVM</h3><p>No exemplo anterior, abstraímos os detalhes de funcionamento de cada FlowView. No entanto, é relevante entender como isso tudo pode ser adaptado para um desenvolvimento com arquitetura <a href="https://www-avanderlee-com.translate.goog/swiftui/mvvm-architectural-coding-pattern-to-structure-views/?_x_tr_sl=en&amp;_x_tr_tl=pt&amp;_x_tr_hl=pt&amp;_x_tr_pto=tc">MVVM</a> ou semelhante. Nesse contexto, não é desejável que cada tela do fluxo conheça e manipule a pilha de navegação. Em vez disso, a view que possui o path entrega uma função de conclusão, e o fluxo apenas avisa que terminou.</p><p>Adicionamos a função de concluir na view pai, e ela será passada pelas próximas telas.</p><pre>// HomeView.swift<br><br>enum Rota: Hashable {<br>    <br>    case novaReceita<br>    case novoEvento<br>    case perfil(usuario: String)<br>}<br><br><br>struct HomeView: View {<br>    <br>    @State private var path = NavigationPath()<br>    <br>    var body: some View {<br>        NavigationStack(path: $path) {<br>            VStack(spacing: 20) {<br>                <br>                Button(&quot;Publicar nova receita&quot;) {<br>                    path.append(Rota.novaReceita)<br>                }<br>                <br>                Button(&quot;Publicar novo evento&quot;) {<br>                    path.append(Rota.novoEvento)<br>                }<br>                <br>                Button(&quot;Visualizar perfil&quot;) {<br>                    path.append(Rota.perfil(usuario: &quot;Fulaninho&quot;))<br>                }<br>            }<br>            .navigationDestination(for: Rota.self) { rota in<br>                destino(para: rota)<br>            }<br>        }<br>    }<br>    <br>    // esta função será chamada nas views filhas para esvaziar a pilha<br>    private func concluirCriacao() {<br>     path = NavigationPath()<br>    } <br>    <br>    @ViewBuilder<br>    private func destino(para rota: Rota) -&gt; some View {<br>        switch rota {<br>        case .novaReceita:<br>            CriarReceitaFlowView(aoConcluir: concluirCriacao)<br>        case .novoEvento:<br>            CriarEventoFlowView(aoConcluir: concluirCriacao)<br>        case .perfil(let usuario):<br>            PerfilView(usuario: usuario)<br>        }<br>    }<br>}</pre><p>Na FlowView, inicializamos com a ViewModel correspondente e com a função de retorno para a view pai:</p><pre>struct CriarReceitaFlowView: View {<br>    <br>    @State private var viewModel: CriarReceitaViewModel<br>    <br>    // inicialização da função de conclusão<br>    init(aoConcluir: @escaping () -&gt; Void) {<br>        let viewModel = CriarReceitaViewModel()<br>        viewModel.aoConcluir = aoConcluir<br>        _viewModel = State(initialValue: viewModel)<br>    }<br>    <br>    <br>    var body: some View {<br>        <br>        // chama a tela correspondente a cada etapa definida no viewModel<br>        Group {<br>            switch viewModel.etapa {<br>            case .criar:        CriarReceitaView()<br>            case .ingredientes: IngredientesReceitaView()<br>            case .preparo:      ModoDePreparoView()<br>            case .detalhes:     PreparoReceitaView()<br>            case .publicar:      PublicarReceitaView()<br>            // outras telas de fluxo...<br>            }<br>        }<br>        .environment(viewModel) // permite que viewModel seja acessada por qualquer view filha<br>    }<br>}</pre><p>E, ao concluir, chamamos a função aoConcluir() quando quisermos encerrar o fluxo e voltar para a tela inicial.</p><pre>// CriarReceitaViewModel.swift<br><br>@Observable<br>final class CriarReceitaViewModel {<br>    var etapa: Etapa = .criar<br><br>    var aoConcluir: (() -&gt; Void)? // declara a função de concluir<br><br>    func publicar() async {<br>        // envia a receita...<br>        aoConcluir?()   // avisa a view dona do path que o fluxo terminou<br>    }<br>}</pre><p>Desta maneira, obtém-se um desenvolvimento mais limpo, com menor acoplamento entre telas e respeito ao princípio de responsabilidade única, o que contribui para facilitação da testabilidade e escalabilidade.</p><p>Essa é apenas uma maneira de lidar com a navegação em SwiftUI, mas você pode se sentir livre para adaptar para necessidades específicas do seu projeto. Uma prática interessante para modularizar ainda mais as funcionalidades seria criar uma classe <strong>Router</strong> unificando todas as operações aplicáveis à pilha, de modo que cada <strong>viewModel</strong> fica apenas responsável por instaciar o Router.</p><h3>Fontes</h3><ul><li><a href="https://developer.apple.com/documentation/swiftui/navigationpath">NavigationPath | Apple Developer Documentation</a></li><li><a href="https://developer.apple.com/documentation/swiftui/understanding-the-navigation-stack">Understanding the navigation stack | Apple Developer Documentation</a></li><li><a href="https://www.createwithswift.com/taking-control-of-your-navigation-in-swiftui-with-navigationpath/">Taking control of your navigation in SwiftUI with NavigationPath</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ab6dffb2eed0" width="1" height="1" alt=""><hr><p><a href="https://medium.com/appledeveloperacademy-ufpe/navega%C3%A7%C3%A3o-profunda-em-swiftui-entenda-como-utilizar-navigationpath-para-lidar-com-fluxos-de-telas-ab6dffb2eed0">Navegação profunda em SwiftUI: entenda como utilizar NavigationPath para lidar com fluxos de telas…</a> was originally published in <a href="https://medium.com/appledeveloperacademy-ufpe">Apple Developer Academy | UFPE</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Explicando comandos de Git como se você tivesse 5 anos]]></title>
            <link>https://medium.com/appledeveloperacademy-ufpe/explicando-comandos-de-git-como-se-voc%C3%AA-tivesse-5-anos-206e9a08f068?source=rss----b27283248e96---4</link>
            <guid isPermaLink="false">https://medium.com/p/206e9a08f068</guid>
            <category><![CDATA[github]]></category>
            <category><![CDATA[repositories]]></category>
            <category><![CDATA[git]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[coding]]></category>
            <dc:creator><![CDATA[Luisa Simões]]></dc:creator>
            <pubDate>Thu, 22 Oct 2026 13:04:26 GMT</pubDate>
            <atom:updated>2026-10-22T13:04:24.764Z</atom:updated>
            <content:encoded><![CDATA[<h4>Você já se sentiu completamente perdido ao desenvolver um projeto colaborativo? Sem saber qual comando utilizar no terminal ou com medo de fazer alguma coisa errada e acabar comprometendo o trabalho de todo mundo?</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/0*mQDuHqp8HaqXqrmp" /></figure><p>E u sempre tive uma dificuldade muito grande de compreender como funcionavam os comandos de Git, tudo sempre pareceu muito abstrato e difícil de absorver. Sempre precisava perguntar para meus colegas qual era o próximo passo. Até que um dia resolvi explicar pra mim mesma como se tivesse 5 anos e, de repente, tudo pareceu mais simples do que realmente era.</p><h3>1. Introdução: o que é Git?</h3><p>O Git é um sistema de controle de versões, ou seja, ele armazena todas as versões de um software, cada mudança feita ao longo do processo de desenvolvimento é mapeada. Isso é fundamental para a construção de um código colaborativo, se uma pessoa sem querer causou um bug no projeto, é possível facilmente retornar para a versão prévia.</p><p>Porém, para usufruir desse sistema é preciso entender os comandos que você precisa direcionar à sua máquina, o que muitas vezes pode parecer confuso, principalmente, para quem está iniciando sua trajetória como pessoa desenvolvedora. Para mim essas instruções sempre ficaram muito abstratas e vagas, foi aí que decidi utilizar a abordagem de explicar para mim mesma com o método <em>“explain like i&#39;m 5”</em> (como se tivesse 5 anos).</p><h3>2. “Explain Like I’m Five”: como funciona essa abordagem?</h3><p>O método de explicar para alguém como se essa pessoa tivesse 5 anos surgiu na comunidade do Reddit, através do subreddit: “Explain like I’m five”; com o intuito de que as pessoas fizessem perguntas complexas e recebessem repostas simples e esclarecedoras livres de julgamento, como se os usuários estivessem explicando para uma criança. Entretanto, isso não implica que precisam ser respostas rasas e infantilizados, geralmente as publicações recorrem a analogias e simplificações como meio de aproximar pessoas de assuntos que possam parecer inicialmente intimidadores e de difícil compreensão.</p><p>E foi pensando nisso que cheguei a seguinte pergunta: <strong>E se aprender Git fosse como construir um castelo de LEGO?</strong></p><h3>3. Comandos do Git: vamos construir um castelo de LEGO?</h3><p>Imagine que você está no jardim de infância. Durante o recreio, você e seus amigos decidem começar um projeto muito importante: construir o maior e mais incrível castelo de LEGO que a escola já viu. Depois de muito trabalho, vocês finalmente terminam a base do castelo. Como todo bom projeto, vocês querem garantir que ninguém estrague o que já foi construído.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*v5h4gAcLmZPWZRlPZYctUw.png" /></figure><h3>3.1 Git init: o começo de tudo</h3><p>Antes de continuar, vocês decidem criar um diário para registrar o desenvolvimento do castelo. Nesse diário, poderão guardar informações sobre cada etapa importante da construção.</p><p>É exatamente essa a ideia do comando: git init</p><p>Ele inicializa um repositório Git, permitindo que o histórico de alterações do projeto comece a ser registrado. Agora vocês têm um sistema preparado para acompanhar a evolução do castelo!</p><h3>3.2 Main: nosso castelo principal</h3><p>A base do castelo está pronta e vocês decidem que ela será o ponto de partida para as próximas construções. Vamos chamar essa linha principal de desenvolvimento de main. Ela representa a versão principal do projeto, a partir da qual podemos criar outras linhas de desenvolvimento.</p><p>Porém o recreio está acabando e ainda faltam muitas coisas. Vocês precisam construir torres, muralhas e até um navio para proteger o castelo. Como fazer tudo isso sem correr o risco de estragar a construção principal?</p><h3>3.3 Git branch: cada ideia no seu próprio espaço</h3><p>Para continuar o projeto, vocês decidem criar espaços separados de construção. Dessa maneira, cada pessoa pode experimentar novas ideias sem modificar diretamente o castelo principal.</p><p>Você fica responsável por duas tarefas: construir a torre leste e desenvolver um navio. Para organizar o trabalho, cria duas branches:</p><p>git branch torre-leste</p><p>git branch navio</p><p>Uma <em>branch</em> é uma linha de desenvolvimento que permite trabalhar em alterações separadamente. Entretanto, é importante lembrar que as branches não pertencem necessariamente a pessoas diferentes. Você pode criar várias branches para desenvolver funcionalidades distintas, assim como várias pessoas podem colaborar em uma mesma branch.</p><h3>3.4 Git switch: mudando de construção</h3><p>Você decide começar pela torre leste. Para entrar na branch correspondente, utiliza:</p><p>git switch torre-leste</p><p>Agora você pode trabalhar nas alterações dessa branch.</p><h3>3.5 Git stash: guardando as peças para depois</h3><p>Imagine que você está montando a torre leste. Existem peças espalhadas por todos os lados, algumas paredes estão pela metade e você ainda não terminou essa etapa. No entanto, como você decidiu começar o navio, não quer perder o progresso da torre, mas também não quer registrar uma etapa inacabada no histórico principal da construção. Então pega uma caixa, coloca suas peças e instruções provisórias lá dentro e guarda tudo para continuar depois.</p><p>Essa caixa é o nosso git stash</p><p>Esse comando guarda temporariamente as alterações locais que ainda não foram commitadas, deixando a área de trabalho limpa para outras atividades. Agora você pode mudar para a branch do navio:</p><p>git switch navio</p><p>Depois de trabalhar no navio, decide voltar para a torre:</p><p>git switch torre-leste</p><p>Mas espere! Onde estão as peças que você tinha guardado? Elas continuam na caixa! Para recuperá-las, basta utilizar:</p><p>git stash pop</p><p>Esse comando reaplica as alterações guardadas e, se a operação for bem-sucedida, remove a entrada correspondente do stash. Agora você pode continuar a construção exatamente de onde parou!</p><p><em>Obs: Na prática, nem toda troca de branch exige um stash. Ele é útil quando precisamos guardar temporariamente alterações que poderiam interferir na mudança de contexto.</em></p><h3>4. Git commit: uma fotografia para o nosso diário</h3><p>Depois de muito trabalho, você finalmente termina a torre leste. Ela ficou incrível. Mas como garantir que essa etapa da construção fique registrada?Lembra do diário que criamos no início? Chegou a hora de utilizá-lo.</p><p>Esse é o momento de registrar essa etapa da construção. Você tira uma fotografia da torre e escreve uma pequena descrição no diário: “Torre leste finalizada”. No Git, fazemos isso utilizando:</p><p>git commit -am &quot;Finaliza a torre leste&quot;</p><blockquote>Obs: -am, o &quot;a&quot; vem de add e o &quot;m&quot; de message</blockquote><p>O commit registra um novo ponto no histórico do projeto com o conteúdo preparado na staging area. Assim, podemos acompanhar como o castelo evoluiu ao longo do tempo. E, se algo der errado no futuro, teremos um histórico que poderá nos ajudar a recuperar versões anteriores.</p><h3>5. Git merge: juntando nossas construções</h3><p>Depois de vários recreios, a torre leste finalmente está pronta. Mas existe um problema: ela foi desenvolvida em uma branch separada e ainda não foi incorporada à linha principal do castelo.</p><p>Chegou a hora de juntar tudo! Primeiro, você volta para a branch principal:</p><p>git switch main</p><p>Depois, utiliza:</p><p>git merge torre-leste</p><p>Esse comando integra as alterações da branch torre-leste à branch atual, main. É como pegar a torre que você construiu separadamente e incorporá-la ao castelo principal. Agora todos podem aproveitar a nova construção.</p><p>Mas e se duas pessoas tiverem modificado a mesma parte do castelo de maneiras diferentes?</p><h3>6. Merge conflict: quando duas ideias não combinam</h3><p>Imagine que você e seu amigo Pedro estavam trabalhando em branches diferentes. Você decidiu que a entrada do castelo deveria ter uma porta vermelha. Pedro, por outro lado, decidiu que a mesma porta deveria ser azul. Quando vocês tentam juntar as alterações, surge uma dúvida: Afinal, qual cor a porta deve ter?</p><p>Em determinadas situações, o Git não consegue decidir automaticamente como combinar alterações incompatíveis. Isso é chamado de <em>merge conflict</em>.</p><p>E, ao contrário do que pode parecer, um conflito não significa que seu projeto foi destruído! Significa apenas que o Git precisa da sua ajuda para decidir como resolver determinadas alterações. Vocês podem conversar e decidir manter a porta vermelha, a azul ou até criar uma solução completamente diferente.</p><p>O importante é lembrar: <strong>conflitos fazem parte do desenvolvimento colaborativo e não precisam ser motivo de pânico.</strong></p><h3>7. Git push, Git fetch e Git pull: compartilhando o castelo com os amigos</h3><p>Até agora, você aprendeu a criar branches, registrar alterações e integrar diferentes linhas de desenvolvimento. Entretanto, existe um último problema. Você construiu uma torre incrível em casa, só que seus amigos ainda não conseguem ver as alterações que estão no seu computador.</p><p>Para compartilhar o trabalho, vocês utilizam um repositório remoto, que pode estar hospedado no GitHub. Pense nele como um espaço compartilhado onde o projeto e seu histórico podem ser armazenados e acessados pela equipe.</p><h3>7.1 Git push: enviando suas alterações</h3><p>Você terminou sua parte da construção e quer compartilhar os novos registros com seus amigos. Para enviar seus commits ao repositório remoto, pode utilizar:</p><p>git push origin main</p><p>Nesse exemplo, origin é o nome do repositório remoto e main é a branch cujos commits você deseja enviar. É como atualizar o projeto compartilhado com as novas etapas que você registrou.</p><p>A depender de como você criou seu repósitorio, se foi direto em uma plataforma de repositórios Git, como o Github, e já tem ela clonada em sua máquina (ver sobre git clone se você não conhece), o comando git push origin main vai funcionar, entretanto, se você ainda não tem essa branch no Github (remoto) será preciso colocar argumentos: git push origin -u main ou git push origin — upstream main (-u e — upstream) sao as mesmas coisas.</p><p>Depois que você usou o -u pela primeira vez, as duas branches já estão conectadas (localmente e remota). A partir daí, basta digitar git push, pois o Git já sabe que deve enviar as alterações para a origin na branch correspondente automaticamente, ou seja, não será mais preciso o origin -u no comando.</p><blockquote>Obs: O uso do origin é só na primeira vez.</blockquote><h3>7.2 Git fetch: descobrindo as novidades do castelo</h3><p>Imagine que, enquanto você estava em casa construindo sua torre, seus amigos continuaram trabalhando no castelo da escola. Pedro construiu uma muralha, Maria adicionou um jardim e João decidiu modificar a entrada principal.</p><p>No entanto, você ainda não sabe de nada disso. Curioso para descobrir o que aconteceu durante sua ausência, você pede aos seus amigos que enviem as informações sobre as últimas modificações do castelo.</p><p>É exatamente isso que o git fetch faz!</p><p>git fetch origin</p><p>Esse comando busca as atualizações do repositório remoto e atualiza as referências remotas armazenadas no seu computador, <strong>sem modificar automaticamente os arquivos em que você está trabalhando</strong>. É como receber fotografias das novas construções dos seus amigos, mas sem adicionar nenhuma dessas mudanças ao seu próprio castelo.</p><p>Assim, você consegue descobrir o que mudou antes de decidir se deseja incorporar as novidades ao seu projeto. Mas e se você quiser trazer essas alterações para a sua construção?</p><p>É aí que entra o nosso próximo comando!</p><h3>7.3 Git pull: trazendo as novidades para o castelo</h3><p>Agora que você já sabe o que seus amigos construíram, decide que também quer incorporar essas alterações ao seu castelo.</p><p>Para isso, utiliza:</p><p>git pull origin main</p><p>Diferentemente do git fetch, o git pull não apenas busca as atualizações do repositório remoto, mas também tenta integrá-las à sua branch atual. É como receber as fotografias das construções dos seus amigos e, em seguida, atualizar seu próprio castelo para incluir as novidades. De maneira simplificada, podemos pensar que:</p><p><strong>Git fetch = descobrir o que mudou.</strong></p><p><strong>Git pull = buscar as mudanças e tentar incorporá-las ao seu trabalho.</strong></p><p>E sabe o mais interessante? O git pull já realiza uma operação de fetch antes de integrar as alterações, ou seja, você não precisa executar os dois comandos em sequência obrigatoriamente.</p><p>No final das contas, o git pull é um git fetch seguido de um git merge, ele busca e tenta mesclar (verifica se terá conflito)</p><h3>8. Afinal, Git é tão complicado assim?</h3><p>Quando comecei a aprender Git, eu tentava decorar comandos sem compreender exatamente o que eles faziam. Sabia que precisava utilizar git commit ou git push, mas nem sempre entendia o motivo. E acho que esse é um dos maiores desafios de aprender ferramentas de desenvolvimento: muitas vezes tentamos memorizar soluções antes de compreender os problemas que elas resolvem.</p><p>A analogia do castelo de LEGO não explica todas as particularidades do Git, mas nos ajuda a visualizar o papel de cada comando dentro de um processo de desenvolvimento. E, quando entendemos o que está acontecendo, aqueles comandos que antes pareciam tão abstratos começam a fazer muito mais sentido.</p><p>No fim das contas, aprender Git não precisa ser sobre decorar uma sequência de instruções. Pode ser simplesmente sobre entender como construir, registrar, experimentar e compartilhar algo com outras pessoas. Mesmo que esse algo seja apenas um castelo de LEGO.</p><h4>Contato:</h4><ul><li><a href="https://github.com/luisamsimoes">luisamsimoes - Overview</a></li><li><a href="https://www.linkedin.com/in/lu%C3%ADsa-sim%C3%B5es-149356378/?isSelfProfile=true">Luísa Simões - Recife, Pernambuco, Brazil | Professional Profile | LinkedIn</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=206e9a08f068" width="1" height="1" alt=""><hr><p><a href="https://medium.com/appledeveloperacademy-ufpe/explicando-comandos-de-git-como-se-voc%C3%AA-tivesse-5-anos-206e9a08f068">Explicando comandos de Git como se você tivesse 5 anos</a> was originally published in <a href="https://medium.com/appledeveloperacademy-ufpe">Apple Developer Academy | UFPE</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Infografia e pitch para quem não é designer]]></title>
            <link>https://medium.com/appledeveloperacademy-ufpe/infografia-e-pitch-para-quem-n%C3%A3o-%C3%A9-designer-325f4e9e65d4?source=rss----b27283248e96---4</link>
            <guid isPermaLink="false">https://medium.com/p/325f4e9e65d4</guid>
            <category><![CDATA[design]]></category>
            <category><![CDATA[pitch-deck]]></category>
            <category><![CDATA[presentation-design]]></category>
            <dc:creator><![CDATA[Clara Souza]]></dc:creator>
            <pubDate>Thu, 22 Oct 2026 12:47:10 GMT</pubDate>
            <atom:updated>2026-10-22T12:47:09.041Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ic3dFkNkgmNZ0dIn" /><figcaption>Photo by <a href="https://unsplash.com/@xteemu?utm_source=medium&amp;utm_medium=referral">Teemu Paananen</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Existe algo que fica à espreita em todos os projetos que tem a pretensão de serem lançados a público. Você passa meses idealizando uma solução, prototipa, coda, se estressa com o versionamento até que finalmente está pronto, e aí o monstro aparece. Quem diria, a solução que você projetou para pessoas PRECISA alcançar pessoas, e automaticamente se torna necessário promover e defender a sua proposta com unhas e dentes em diferentes situações, com uma boa estratégia de marketing, e apresentações com um bom pitch de produto.</p><p>É por meio do pitch que as pessoas terão o primeiro contato com a sua ideia, em poucos minutos você precisa comunicar qual é o problema, quais são as pessoas afetadas por ele, a solução proposta e como ela agrega valor para esses usuários, tudo isso com uma boa linearidade narrativa, dados relevantes e muito, mas muito ensaio.</p><p>Além de uma boa linha narrativa, um bom pitch precisa de apoio visual para comunicar com profissionalismo, credibilidade, maior precisão e interatividade o seu produto, e isso é feito normalmente através de apresentações com slides.</p><p>Quando comecei a fazer apresentações foi de forma bem básica em situações mais acadêmicas seguindo um padrão bastante ABNT, muito texto, informações em tópicos com marcadores, um formato bastante quadrado com uma hierarquia bem padrão de título, corpo de texto e imagens. <strong>Formatos assim servem mais como forma de apoio ao apresentador do que para ser algo que de fato acrescente na experiência de quem está assistindo.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HmtkTXACHXPlaGi76wmXlQ.png" /><figcaption>Exemplo de formatação não interessante para pitch contendo marcadores e informarções condensadas (template google slides)</figcaption></figure><p>Apresentações de Slides em pitches servem para <strong>apoiar</strong> a sua fala, <strong>simplificar</strong> conceitos e dados, além, é claro, de <strong>chamar atenção</strong> e <strong>causar impacto</strong>, por isso devem ser sucintos, interessantes, dinâmicos, interativos e estéticamente satisfatórios. Através do conhecimento dos fundamentos de design alcançar esses objetivos fica mais simples.</p><p>Hierarquia, contraste e escala entre os elementos, é o que vai comunicar o que você deseja passar, destacar pontos importantes e tornar a sua apresentação interessante ao público.</p><p><strong>É importante estar atento à algumas coisas para ter um bom resultado na construção desse tipo de material:</strong></p><ol><li><strong>Faça os slides com base no seu roteiro de apresentação</strong>, dessa forma fica tudo bem sincronizado e de fato fomentará a sua performance.</li></ol><p><strong>2. Tenha uma identidade aplicada</strong>, utilize dos elementos visuais que tem a ver com o seu produto e mantenha uma unidade em todas as páginas (cores, texturas, imagens).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*w1DmMfi5KohgNRgP2llW9A.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iOhYQdkA-01qXIDE67zUQw.png" /><figcaption>À esquerda temos um bom uso da identidade do aplicativo, que é relacionado a café e possui como cor da marca um marrom escuro, além disso há a utilização de bom contraste e ênfase em palavras chave relacionadas a funcionalidade do produto, enquanto na direita temos um exemplo de contraste ruim entre fundo e texto além da cor rosa não representar a marca.</figcaption></figure><p><strong>3. Alimente sua narrativa com imagens</strong>, em alguns momentos uma imagem fala mais que mil palavras.</p><p><strong>4. Use e abuse de escala:</strong> quer dar ênfase em algum contexto ou tem uma imagem que é crucial para a sua fala? Dê a ela o devido espaço, aumente seu tamanho, vaze o conteúdo ou o torne o centro da página.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xNEvzUnVFvT6ttl-Y0kUBA.png" /><figcaption>Imagem expandida de vários grãos de café utilizada para dar ênfase a quantidade de consumo no brasil.</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ywrhK7f_Xq2o4SVRg0w1iQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0cidAEAIoS8m-HgcHsKBlw.png" /><figcaption>Composição com elementos em uma escala não convencional à esquerda.</figcaption></figure><p><strong>5. Invés de usar marcação de tópicos</strong> utilize outras formas de demarcar importância (containers, opacidade, tamanho, pesos de fonte).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UMwhuV0tBrk2IMCSoT4dwA.png" /><figcaption>Página topicalizada sem marcadores, nesse caso o destaque é feito a partir de pesos diferentes na fonte.</figcaption></figure><p><strong>6. Descondense informações</strong>, caso necessário divida o tópico em mais de um slide:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rG3eSwESIydhNGwlwhdyHA.png" /><figcaption>Exemplo de slide condensado com muita informação.</figcaption></figure><p>7. Utilize <strong>mockups</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GH8U4FzwLHj6Qh320YhFvQ.png" /><figcaption>Aplicação de mockups, identidade visual e tipografias.</figcaption></figure><p>Além disso, há outras informações que merecem uma atenção especial nas suas páginas.</p><p>Vem comigo: <strong>você encontra uma pesquisa massa que dá credibilidade ao seu projeto</strong> e a transcreve para o slide com texto puro de 3 linhas em um slide. As pessoas que estão vendo a apresentação desviam a atenção lendo o que tem no slide, desfocando da sua fala, ou até mesmo perdem o interesse no que está na tela.</p><p>Transformar dados em uma informação mais <strong>impactante e acessível</strong> é uma grande habilidade do design da informação e<strong> infografia</strong> que prezam por <strong>descomplicar tópicos e temas complexos</strong> ou difíceis de serem abordados, <strong>por meio de representações visuais ao invés de texto.</strong> E é aí que você pode usar toda a sua criatividade para transformar algo cansativo em um momento de <strong>impacto na sua apresentação</strong>.</p><p>Brincar com elementos, ilustrações, materiais, escalas e senso de quantidade são ideias muito bem-vindas. Veja alguns exemplos abaixo: <em>(Os mesmos conceitos de design estão presentes nessa fase, além da gestalt que é aplicado quando estamos trabalhando com a visualização de elementos agrupados.)</em></p><ol><li><strong>Dados através de elementos gráficos</strong></li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8AWc8n9BFPNNkR4TrelJOA.png" /><figcaption>Representação da porcentagem mencionada através de icones.</figcaption></figure><p><strong>2. Números grandes e expressivos</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*K5KgFEp1aRn9XELaVZfVqg.png" /><figcaption>Destacar um número importante de uma pesquisa automaticamente levanta curiosidade nas pessoas.</figcaption></figure><p>3. Resuma a informação e mostre apenas o essencial (não esqueça de mencionar a fonte).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8AWc8n9BFPNNkR4TrelJOA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OlcKEZXszsYEUEATO-YawA.png" /><figcaption>Comparação entre dados resumidos e trechos na íntegra.</figcaption></figure><p>Essas são algumas dicas que podem não ser milagrosas mas são ótimas práticas para avaliar se a sua apresentação está comunicando bem a ideia do seu produto. Quando for construir o seu material não esqueça também de procurar por boas referências para ter mais ideias de como representar seu roteiro.</p><ul><li><a href="https://www.infodesign.org.br/infodesign/article/view/136/114">View of Infografia: Conceito e Prática</a></li><li><a href="https://visme-co.translate.goog/blog/what-is-a-pitch-deck/?_x_tr_sl=en&amp;_x_tr_tl=pt&amp;_x_tr_hl=pt&amp;_x_tr_pto=tc">What is a Pitch Deck? Examples, Tips and Templates (2021)</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=325f4e9e65d4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/appledeveloperacademy-ufpe/infografia-e-pitch-para-quem-n%C3%A3o-%C3%A9-designer-325f4e9e65d4">Infografia e pitch para quem não é designer</a> was originally published in <a href="https://medium.com/appledeveloperacademy-ufpe">Apple Developer Academy | UFPE</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Antes do primeiro slide: como montar a narrativa do seu Pitch]]></title>
            <link>https://medium.com/appledeveloperacademy-ufpe/antes-do-primeiro-slide-como-montar-a-narrativa-do-seu-pitch-8c5869043476?source=rss----b27283248e96---4</link>
            <guid isPermaLink="false">https://medium.com/p/8c5869043476</guid>
            <category><![CDATA[presentations]]></category>
            <category><![CDATA[pitch-deck]]></category>
            <category><![CDATA[call-to-action]]></category>
            <dc:creator><![CDATA[Danilo Lira]]></dc:creator>
            <pubDate>Thu, 22 Oct 2026 12:35:24 GMT</pubDate>
            <atom:updated>2026-10-22T12:35:23.405Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3rjsDc8fXR3onvFqz1Gdmw.png" /></figure><p><em>Tudo muda quando só temos poucos minutos para contar nossa história. É preciso estar preparado para honrá-la quando a oportunidade chegar.</em></p><p>Neste artigo, vamos aprender a desenvolver do zero <strong>a estrutura</strong> e <strong>o raciocínio</strong> de um bom <em>pitch</em>, a partir da tão assustadora folha em branco, seja ela uma Chamex ou do Google Docs.</p><p>Mas antes, de forma prática, precisamos entender <strong>o que é </strong>um <em>pitch</em>.</p><p><strong><em>Pitch deck </em>é uma apresentação curta para vender uma ideia, um produto ou um serviço, geralmente realizada para captar recursos de investidores.</strong></p><p>Se eu pudesse definir os princípios básicos de um <em>pitch</em> em 3 palavras, diria simples, direto e afiado.</p><p><strong>Simples</strong> porque precisa ser compreendido por todos.<br><strong>Direto</strong> porque, de forma geral, não teremos tempo para desperdiçar.<br><strong>Afiado</strong> porque sua força está em encontrar o tom da apresentação.</p><h3><strong>3 passos para começar a construir o seu <em>pitch</em></strong></h3><ol><li><strong>Pensar para quem é a apresentação:</strong> investidor, parceiro ou público final (clientes)? Definir isso será essencial para resolver qual será o tom adotado, além dos argumentos de convencimento. Um <em>pitch</em> para investidor provavelmente será um pouco menos descontraído, enquanto o público final de maneira geral não tem muito interesse em escalabilidade e na projeção de números do seu negócio. É nesse momento que você vai avaliar tudo isso.</li><li><strong>Escrever tópicos para não deixar nada de fora:</strong> pessoalmente, gosto de começar no papel porque me ajuda a pensar, mas você pode fazer isso de forma digital também. O importante é conseguir visualizar os pontos-chave do seu <em>pitch</em> para conferir ao final se contemplou todos.</li><li><strong>Desenvolver o raciocínio completo:</strong> o <em>pitch</em> consiste em apresentar um problema, uma solução e uma chamada para ação. Ou seja, você parte de um ponto A, passa por um ponto B e finaliza em um ponto C. E uma forma muito poderosa de fazer isso é com <em>storytelling</em>.</li></ol><p>Ao finalizar esses 3 passos, <strong>você deve estar seguro de qual a história você quer contar e para quem,</strong> levando em consideração os personagens, os desafios que ele enfrenta, a resolução da história e o que você quer provocar com ela no seu ouvinte.</p><h3><strong>Finalmente, como desenvolver o pitch?</strong></h3><p>Você vai precisar escrever tudo que vai ser falado, cada frase, um roteiro da sua apresentação. Não precisa decorar uma “fala” como se fosse um trabalho de escola, mas é necessário conhecer a estrutura narrativa com segurança.</p><h4><strong>Apresente o problema que você precisou resolver</strong></h4><p>Antes de revelar qual é a sua ideia, produto ou serviço, comece pensando em um início que prenda a atenção e desperte a curiosidade da audiência para querer saber mais sobre a sua apresentação. Pode ser uma pergunta, uma frase de impacto ou a apresentação de um personagem, por exemplo. Mas isso deve estar atrelado ao problema que você resolveu.</p><h4><strong>Ancore a solução do problema com dados</strong></h4><p>Seja específico e mostre dados para mostrar como a sua solução resolve o problema apresentado anteriormente. No caso de um aplicativo para celular, por exemplo, você pode falar sobre as funcionalidades que resolvem uma dificuldade ou uma necessidade do seu usuário.</p><h4><strong>Finalize com uma chamada para ação</strong></h4><p>Mais perguntas importantes para se fazer: qual é o objetivo da sua apresentação? O que você deseja que a audiência faça logo após finalizar o seu <em>pitch</em>?</p><p>Vamos imaginar que você deseja que a audiência baixe seu aplicativo ou que investidores te procurem para continuar uma conversa de negócios. Como a ideia nessa etapa é facilitar que a ação seja realizada, então, seguindo com esses exemplos, você pode colocar nos slides o <em>QR Code</em> que leve para o aplicativo na loja ou para o contato profissional no <em>WhatsApp Business</em>.</p><p>Outra boa prática é terminar o <em>pitch</em> com uma frase de efeito que sintetize a solução que você apresentou, como se fosse um mote ou um slogan.</p><h3><strong>O ritmo do pitch</strong></h3><p>Quando você estiver escrevendo o roteiro da sua narrativa, preste atenção no fluxo das informações e nos ganchos entre as frases para não quebrar o ritmo da apresentação.</p><p>Evite interrupções como <em>“agora eu vou falar sobre tal assunto…”</em> porque isso não acrescenta nenhuma informação relevante para o público e ainda te faz perder segundos valiosos. Pode ir direto ao tópico, sem medo.</p><h3><strong>Treinar é a melhor prática antes da apresentação</strong></h3><p><em>Training season is not over!</em> Depois de escrever palavra por palavra do seu <em>pitch</em>, a ideia é começar lendo o texto e <strong>cronometrando o tempo</strong> até se acostumar com a estrutura da apresentação.</p><p>É assim que você vai <strong>criar intimidade com o texto</strong>, <strong>adaptar termos</strong> que pareçam deslocados e <strong>memorizar a estrutura</strong>(o que vem antes, o que vai depois) para não se perder. Logo você vai deixar de precisar ler, porque já sabe <strong>o que e quando</strong> precisa falar.</p><p>Essa etapa é importante para fazer ajustes e <strong>refinar o seu <em>pitch</em></strong>: se sobrou tempo, você pode incluir mais informações, falar sobre testadores, sobre seu modelo de negócio, funcionalidades e próximos passos. Se faltou tempo, é hora de enxugar o texto, limando tudo que não é essencial para o <em>pitch</em>.</p><p>Não esqueça de <strong>treinar junto com os slides</strong> do <em>pitch</em> para casar sua fala com a parte visual da apresentação!</p><h3><strong>Dois parágrafos, antes de dar tchau</strong></h3><p><strong>Ser um bom ouvinte pode te ajudar</strong> a entender como funciona um bom pitch. Busque no Youtube vídeos como <em>“1 minute pitch example”</em> para ter referências sobre ritmo, postura, introdução e chamada para uma ação <em>(call to action)</em> ao final, por exemplo.</p><p>Além de ter referências, <strong>apresentar pitch também é uma questão de prática</strong>, então se ele não sair perfeito de primeira, fique tranquilo. Uma carreira é construída a partir de experiências. Aprenda, adapte e repita até melhorar o que deseja.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8c5869043476" width="1" height="1" alt=""><hr><p><a href="https://medium.com/appledeveloperacademy-ufpe/antes-do-primeiro-slide-como-montar-a-narrativa-do-seu-pitch-8c5869043476">Antes do primeiro slide: como montar a narrativa do seu Pitch</a> was originally published in <a href="https://medium.com/appledeveloperacademy-ufpe">Apple Developer Academy | UFPE</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Delegates e ViewState: gerenciando estados da tela em UIKit]]></title>
            <link>https://medium.com/appledeveloperacademy-ufpe/gerenciando-estados-da-tela-na-uiviewcontroller-uikit-com-mvvm-eedfb755190a?source=rss----b27283248e96---4</link>
            <guid isPermaLink="false">https://medium.com/p/eedfb755190a</guid>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[uikit]]></category>
            <category><![CDATA[delegate-pattern]]></category>
            <dc:creator><![CDATA[Pedro Vítor]]></dc:creator>
            <pubDate>Wed, 25 Feb 2026 13:40:29 GMT</pubDate>
            <atom:updated>2026-02-25T13:40:29.685Z</atom:updated>
            <content:encoded><![CDATA[<h3><strong>Introdução</strong></h3><p>Ao desenvolver um aplicativo, muitas vezes precisamos de um banco de dados remoto para armazenar os dados dos nossos usuários.<br>Surge então a dúvida:</p><ul><li>Como atualizar a tela <strong>automaticamente</strong> quando os dados chegarem?</li><li>Como adicionar animações como <em>shimmering </em>(também conhecido como Skeleton<em>) </em>ou UIProgressView durante o carregamento?</li><li>E, no caso de falha, como exibir um <strong>erro</strong> claro para o usuário?</li></ul><p>Para resolver tudo isso, podemos criar uma variável de estado da tela e atualizá-la conforme cada etapa do processamento no banco.</p><h3>ViewState</h3><p>Vamos criar um enum para representar os possíveis estados da view:</p><pre>enum ViewState {<br>    case idle<br>    case loading<br>    case updating<br>    case error(Error)<br>    case success<br>}</pre><h4>Idle</h4><p>Ele é o estado que surge logo <strong>antes das busca ao banco ser chamada</strong>. Nele, podemos deixar a UIView em <strong>branco</strong> ou exibir apenas os <strong>elementos imutáveis</strong> — que não dependem dos dados carregados.</p><h4>Loading e Updating</h4><p>É nesses estados que a tela fica <strong>enquanto o app espera a execução de operações no banco</strong>.</p><p>Por que dividir em dois? Poderíamos simplesmente utilizar o loading para lidar com todos os tipos de operações de acesso ao banco. Porém, é interessante dividir entre operações de <strong>leitura</strong> (read) e de <strong>modificação</strong> ao banco (create, update, delete).</p><p>Assim, podemos adicionar animações de <strong>shimmering</strong> exclusivamente ao estado de <strong>loading</strong>, e simular o carregamento dos componentes da tela enquanto o app espera a resposta de leituras ao banco.</p><p>Já para operações de modificação realizadas pelo usuário, podemos utilizar o <strong>updating</strong> para adicionar uma animação de carregamento como uma <strong>UIProgressView</strong>. Assim, o usuário espera a conclusão da operação com um feedback visual claro.</p><h4>Success</h4><p>É o caso em que a operação foi concluída com sucesso e a tela está pronta para exibir as informações obtidas. É aqui que vamos atualizar a ViewController com os dados obtidos do banco.</p><h4>Error</h4><p>É o estado da tela em que houve algum <strong>erro na operação do banco</strong> e deve ser repassado para o usuário visualizar na tela.</p><p>Para que possamos exibir componentes ou mensagens diferentes para cada tipo de erro, precisamos saber <strong>qual</strong> erro aconteceu. Para isso, o próprio caso do enum tem um <strong>tipo associado</strong> em que podemos passar o erro retornado da operação. É por isso que declaramos o caso do enum assim:</p><pre>case error(Error)</pre><p>Por exemplo, se houve o erro NetworkFailed:</p><pre>var viewState = ViewState.error(NetworkFailed)</pre><h3>Incorporando o ViewState nas operações do Banco</h3><p>Nesse artigo, usarei a arquitetura MVVM. Assim, as funções que fazem operações ao banco estarão na <strong>ViewModel</strong>, e é nela que vamos gerenciar a nossa variável do tipo ViewState. A UIViewController irá apenas <strong>reagir</strong> aos diferentes estados.</p><p>Para simular, vamos criar um serviço que cria e obtém Tasks. Vamos criar a classe Task:</p><pre>class TaskModel {<br>    var name: String<br>    <br>    init(name: String) {<br>        self.name = name<br>    }<br>}</pre><p>Agora, vamos criar um serviço que &quot;obtém&quot; as tarefas:</p><pre>class TaskService {<br>    func getTasks() async throws -&gt; [TaskModel] {<br>        return [<br>            TaskModel(name: &quot;Tarefa 1&quot;),<br>            TaskModel(name: &quot;Tarefa 2&quot;),<br>            TaskModel(name: &quot;Tarefa 3&quot;),<br>        ]<br>    }<br>}</pre><p>A função acima é assíncrona e gera um erro — que será tratado na ViewModel e retornado para a ViewController exibí-lo.</p><p>Vamos então criar a ViewModel:</p><pre>class ViewModel {<br>    <br>    // MARK: Init<br>    let taskService: TaskService<br><br>    init(taskService: TaskService) {<br>        self.taskService = taskService<br>    }<br>    <br>    // MARK: State<br>    public var viewState: ViewState = .idle<br>    <br>    // MARK: UIData<br>    public var tasks: [TaskModel] = []<br><br>}</pre><p>Injetamos o TaskService* e inicializamos o viewState no estado idle. Além disso, inicializamos um array de Tasks vazio.</p><p>Vamos agora criar a função da ViewModel que chama a TaskService:</p><pre>func fetchTasks() async {<br>    viewState = .loading<br>    <br>    do {<br>        self.tasks = try await taskService.getTasks()<br>        <br>        viewState = .success<br>    } catch {<br>        print(&quot;Erro ao obter tarefas: \(error)&quot;)<br>        viewState = .error(error)<br>    }<br>}</pre><blockquote>*Seria mais adequado utilizar um protocolo para injetar os serviços.</blockquote><p>Nessa função, inicialmente <strong>alteramos o viewState para loading</strong> para indicar que estamos no <strong>meio de uma operação de leitura</strong>.</p><p>Depois, chamamos um <strong>do/catch</strong> pra poder chamar a função getTasks e tratar corretamente o erro. Se não houver erros, o <strong>viewState é alterado para .success</strong> para indicar que a tela pode ser atualizada com os dados novos. Caso contrário, entrará no <strong>catch</strong> com um error e irá printar o erro, além de alterar o <strong>viewState para .error</strong>, passando o erro obtido como tipo associado.</p><p>Poderíamos fazer o mesmo para uma operação do tipo createTask, utilizando o estado updating.</p><h4>UIViewController e UIView</h4><p>Vamos criar a UIViewController e a UIView:</p><pre>class TaskViewController: UIViewController {<br>    <br>    let taskView: TaskView<br>    let viewModel: ViewModel<br>    <br>    init(taskView: TaskView, viewModel: ViewModel) {<br>        <br>        self.taskView = taskView<br>        self.viewModel = viewModel<br>        super.init(nibName: nil, bundle: nil)<br>        <br>    }<br><br>    required init?(coder: NSCoder) {<br>        fatalError(&quot;init(coder:) has not been implemented&quot;)<br>    }<br>    <br>    // MARK: Lifecycle<br><br>    override func viewDidLoad() {<br>        super.viewDidLoad()<br>    }<br>    <br>    override func loadView() {<br>        self.view = taskView<br>    }<br>    <br>}</pre><pre>class TaskView: UIView {<br>    <br>    // MARK: Init<br>    init() {<br>        super.init(frame: .zero)<br>    <br>        setupView()<br>    }<br>    <br>    required init?(coder: NSCoder) {<br>        fatalError(&quot;init(coder:) has not been implemented&quot;)<br>    }<br>    <br>    // MARK: Components<br>    <br>    lazy var label: UILabel = {<br>        let label = UILabel()<br>        <br>        label.translatesAutoresizingMaskIntoConstraints = false<br>        label.font = UIFont.systemFont(ofSize: 18, weight: .bold)<br>        <br>        return label<br>    }()<br>    <br>    // MARK: SetupView<br>    <br>    func setupView() {<br>        setupViewsHierarchy()<br>        setupViewAttributes()<br>        setupConstraints()<br>    }<br>    <br>    func setupViewsHierarchy() {<br>        self.addSubview(label)<br>    }<br>    <br>    func setupViewAttributes() {<br>        <br>    }<br>    <br>    func setupConstraints() {<br>        NSLayoutConstraint.activate([<br>            label.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor),<br>            label.leadingAnchor.constraint(equalTo: leadingAnchor),<br>            label.trailingAnchor.constraint(equalTo: trailingAnchor),<br>            label.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor),<br>        ])<br>    }<br>    <br>}</pre><p>Nesse formato, a <strong>ViewController conhece a ViewModel e a UIView</strong> e pode <strong>acessar valores e funções </strong>de ambas. Assim, podemos obter diretamente as tasks salvas na ViewModel e atualizar nossa UIView quando necessário.</p><p>Porém, <strong>como que a ViewController saberá quando a ViewModel atualizar seus valores</strong>?</p><p>A ViewModel precisaria <strong>alertar</strong> a ViewController de alguma maneira. Porém, a ViewModel <strong>não conhece</strong> a ViewController, e dessa forma não consegue acessar nada interno a ela. É aqui que entram os <strong>Delegates</strong>.</p><h4>Delegate</h4><p>Um Delegate é uma forma de estabelecer uma comunicação entre dois objetos, permitindo que um objeto chame operações de outro objeto.</p><p>No nosso exemplo, a ViewController já conhece a ViewModel, porém a ViewModel não conhece a ViewController. Poderíamos injetar diretamente a ViewController na ViewModel, mas com isso teríamos alguns problemas:</p><pre>class ViewModel {<br>    <br>    let taskService: TaskService<br>    let viewController: TaskViewController<br><br>    init(taskService: TaskService, viewController: TaskViewController) {<br>        self.taskService = taskService<br>        self.viewController = viewController<br>    }<br>    // ...<br>}</pre><ol><li><strong>Referência Circular</strong></li></ol><p>A ViewController depende da ViewModel para ser inicializada, então se a ViewModel também depender da ViewController, isso criaria um <strong>loop</strong>.</p><p>2. <strong>Ciclo de Retenção</strong></p><p>Por conta da referência circular acima e da referência forte (sem uso de <strong>weak</strong>) entre os objetos, ambos se mantém alocados na memória e podem gerar <strong>memory leaks</strong>.</p><p>3. <strong>Violação da Arquitetura</strong></p><p>Não é ideal que a ViewModel <strong>conheça</strong> a ViewController <strong>diretamente</strong>. Ela precisa existir <strong>independentemente</strong> da implementação da UI. Além disso, bastava que a ViewModel conhecesse apenas funções da ViewController que gerenciam o estado da tela, porém ela também conhece e pode acessar todas as outras funções o que não é adequado.</p><p>Para resolver esses problemas, vamos criar um <strong>protocolo</strong> que vai estabelecer a conexão entre a View e ViewController e incluir apenas as funções que importam para essa comunicação.</p><p><strong>A ViewController </strong>será o<strong> </strong>objeto em que são delegadas tarefas (<strong>delegado</strong>) e por isso <strong>implementará o protocolo</strong>. Já a ViewModel será o <strong>delegador</strong> que manda as tarefas e <strong>armazenará uma referência ao delegado</strong>. A vantagem é que <strong>o delegador não conhece quem é o seu delegado</strong>, apenas sabe que ele implementa algumas funções.</p><p>Vamos criar o protocolo ViewDelegate:</p><pre>protocol ViewDelegate: AnyObject {<br>    func didChangeState(state: ViewState)<br>}</pre><p>Como queremos reagir de acordo com o ViewState, vamos criar uma função didChangeState<em>. </em>Essa função será <strong>chamada pela ViewModel quando o estado mudar</strong> mas será <strong>implementada na ViewController.</strong></p><p>Agora, precisamos adicionar a referência do delegate à ViewModel. Faremos assim:</p><pre>weak var delegate: ViewDelegate?</pre><p>A variável é <strong>weak</strong> e <strong>opcional</strong> para evitar os problemas de referência circular e ciclo de retenção.</p><p>Além disso, precisamos também <strong>conectar</strong> nosso viewStateà chamada da função didChangeState. Para isso, vamos adicionar um didSet à variável viewState. O que o didSetfaz é <strong>chamar a closure dentro dele toda vez que o viewState mudar</strong>.<strong> </strong>No caso, chamaremos a função do delegate para avisar ao nosso delegado que o estado mudou e ele precisa fazer algo.</p><pre>public var viewState: ViewState = .idle {<br>    didSet {<br>        delegate?.didChangeState(state: viewState)<br>    }<br>}</pre><p>Nossa ViewModel ficará assim:</p><pre>class ViewModel {<br>    <br>    // MARK: Delegate<br>    weak var delegate: ViewDelegate?<br>    <br>    // MARK: Init<br>    let taskService: TaskService<br><br>    init(taskService: TaskService) {<br>        self.taskService = taskService<br>    }<br>    <br>    // MARK: State<br>    public var viewState: ViewState = .idle {<br>        didSet {<br>            delegate?.didChangeState(state: viewState)<br>        }<br>    }<br>    <br>    // MARK: UIData<br>    public var tasks: [TaskModel] = []<br>    <br>    func fetchTasks() async {<br>        viewState = .loading<br>        <br>        do {<br>            self.tasks = try await taskService.getTasks()<br>            <br>            viewState = .success<br>        } catch {<br>            print(&quot;Erro ao obter tarefas: \(error)&quot;)<br>            viewState = .error(error)<br>        }<br>    }<br><br>}</pre><p>Agora, falta só ajustarmos a nossa ViewController para seguir o protocolo ViewDelegate:</p><pre>class TaskViewController: UIViewController, ViewDelegate {<br>    <br>    let taskView: TaskView<br>    let viewModel: ViewModel<br>    <br>    init(taskView: TaskView, viewModel: ViewModel) {<br>        <br>        self.taskView = taskView<br>        self.viewModel = viewModel<br>        super.init(nibName: nil, bundle: nil)<br>        <br>    }<br><br>    required init?(coder: NSCoder) {<br>        fatalError(&quot;init(coder:) has not been implemented&quot;)<br>    }<br>    <br>    // MARK: Lifecycle<br><br>    override func viewDidLoad() {<br>        super.viewDidLoad()<br>        // Do any additional setup after loading the view.<br>    }<br>    <br>    override func loadView() {<br>        self.view = taskView<br>    }<br>    <br>    // MARK: ViewState<br>    <br>    func didChangeState(state: ViewState) {<br>        <br>    }<br>    <br>    func updateUI() {<br>        self.taskView.label.text = &quot;\(viewModel.tasks[0].name)&quot;<br>    }<br>    <br>}</pre><p>O protocolo requer uma implementação da função didChangeState. Aqui é onde vamos fazer nossa ViewController <strong>reagir às mudanças de estado</strong>.</p><p>Para isso, vamos criar um switch para chamar funções para cada estado:</p><pre>func didChangeState(state: ViewState) {<br>    switch state {<br>    case .idle:<br>        self.stopAnimations()<br>    case .loading:<br>        self.showShimmering()<br>    case .updating:<br>        self.showUpdatingScreen()<br>    case .error(let error):<br>        self.stopAnimations()<br>        self.showError(error: error)<br>    case .success:<br>        self.stopAnimations()<br>        self.updateUI()<br>    }<br>}</pre><p>Assim, podemos implementar funções específicas para cada estado e fazer a tela reagir de acordo.</p><p>Por fim, ainda precisamos modificar o nosso init para indicar que <strong>o delegate da ViewModel é a ViewController</strong>:</p><pre>init(taskView: TaskView, viewModel: ViewModel) {<br>    <br>    self.taskView = taskView<br>    self.viewModel = viewModel<br>    super.init(nibName: nil, bundle: nil)<br>    <br>    viewModel.delegate = self<br>}</pre><p>Nossa ViewController fica assim:</p><pre>class TaskViewController: UIViewController, ViewDelegate {<br>    <br>    let taskView: TaskView<br>    let viewModel: ViewModel<br>    <br>    init(taskView: TaskView, viewModel: ViewModel) {<br>        <br>        self.taskView = taskView<br>        self.viewModel = viewModel<br>        super.init(nibName: nil, bundle: nil)<br>        <br>        viewModel.delegate = self<br>    }<br><br>    required init?(coder: NSCoder) {<br>        fatalError(&quot;init(coder:) has not been implemented&quot;)<br>    }<br>    <br>    // MARK: Lifecycle<br><br>    override func viewDidLoad() {<br>        super.viewDidLoad()<br>    }<br>    <br>    override func loadView() {<br>        self.view = taskView<br>    }<br>    <br>    // MARK: ViewState<br>    <br>    func didChangeState(state: ViewState) {<br>        switch state {<br>        case .idle:<br>            self.stopAnimations()<br>        case .loading:<br>            self.showShimmering()<br>        case .updating:<br>            self.showUpdatingScreen()<br>        case .error(let error):<br>            self.stopAnimations()<br>            self.showError(error: error)<br>        case .success:<br>            self.stopAnimations()<br>            self.updateUI()<br>        }<br>    }<br>    <br>    func stopAnimations() {<br>        hideShimmering()<br>        hideUpdatingScreen()<br>    }<br>    <br>    func showShimmering() {<br>        // adiciona shimmering<br>    }<br>    <br>    func hideShimmering() {<br>        // remove shimmering<br>    }<br>    <br>    func showUpdatingScreen() {<br>        // adiciona progress view<br>    }<br>    <br>    func hideUpdatingScreen() {<br>        // remove progress view<br>    }<br>    <br>    func showError(error: Error) {<br>        // exibe os erros<br>    }<br>    <br>    func updateUI() {<br>        // atualiza dados da tela<br>    }<br>    <br>}</pre><p>Assim, podemos gerenciar de forma simples os diferentes estados da tela e fazer nossa ViewController reagir a eles automaticamente.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=eedfb755190a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/appledeveloperacademy-ufpe/gerenciando-estados-da-tela-na-uiviewcontroller-uikit-com-mvvm-eedfb755190a">Delegates e ViewState: gerenciando estados da tela em UIKit</a> was originally published in <a href="https://medium.com/appledeveloperacademy-ufpe">Apple Developer Academy | UFPE</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Guia: Como Subir um App iOS com Core Data Pré-Populado no GitHub]]></title>
            <link>https://medium.com/appledeveloperacademy-ufpe/guia-como-subir-um-app-ios-com-core-data-pr%C3%A9-populado-no-github-f9d31ac32795?source=rss----b27283248e96---4</link>
            <guid isPermaLink="false">https://medium.com/p/f9d31ac32795</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[core-data-swift]]></category>
            <dc:creator><![CDATA[Gabriel W.A Matias]]></dc:creator>
            <pubDate>Wed, 25 Feb 2026 13:27:18 GMT</pubDate>
            <atom:updated>2026-02-25T13:39:37.003Z</atom:updated>
            <content:encoded><![CDATA[<h3>Contexto</h3><p>Durante o desenvolvimento do nosso aplicativo, meu time e eu nos deparamos com a necessidade de compartilhar uma versão do app já populada com dados via GitHub.</p><p>Isso era essencial, pois o volume de dados iniciais era alto e recriá-los manualmente a cada build seria inviável. No entanto, o Core Data, por padrão, não armazena os dados persistidos diretamente no projeto — eles ficam salvos localmente no ambiente do simulador ou dispositivo.</p><p>Diante desse desafio, surgiu a necessidade de encontrar uma solução prática e eficiente. A ideia que me ocorreu foi rastrear onde o Core Data armazena suas informações, <strong>extrair esses dados e configurá-los para serem pré-carregados automaticamente na inicialização do app</strong>.</p><p>Este artigo tem justamente o objetivo de servir como um guia completo para desenvolvedores que estejam enfrentando esse mesmo cenário.</p><h3>Passo 1: Entendendo onde o Core Data salva os dados</h3><p>Quando você usa o <strong>Core Data</strong> no iOS, o sistema armazena os dados em arquivos SQLite, normalmente localizados na pasta <strong>Application Support</strong> do sandbox da aplicação. Esses arquivos não ficam visíveis no seu projeto Xcode — e é por isso que, ao compartilhar o app via GitHub, os dados não são enviados junto.</p><p>Uma vez que você já esteja com o BD populado. Para localizar os arquivos .sqlite, .sqlite-wal e .sqlite-shm, basta adicionar este print no seu <strong>AppDelegate.swift</strong>:</p><pre>let paths = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true)<br>print(&quot;📂 Caminho do Application Support: \(paths.first ?? &quot;Caminho não encontrado&quot;)&quot;)</pre><blockquote><em>💡 Esse código imprime o caminho do diretório onde o Core Data salva os dados da aplicação, permitindo que você acesse os arquivos diretamente via Finder.</em></blockquote><p><strong>Agora:</strong></p><ol><li><strong>Rode o app no simulador.</strong></li><li><strong>Copie o caminho impresso no console.</strong></li><li>No Finder, use o menu <strong>Ir → Ir para a pasta…</strong> (Shift + Command + G) e cole o caminho.</li></ol><p>Lá você verá os arquivos:</p><ul><li>DataBase.sqlite</li><li>DataBase.sqlite-wal</li><li>DataBase.sqlite-shm</li></ul><p>Perfeito, uma vez que você tenha encontrado. Esses são os <strong>três arquivos que você precisa</strong> para compartilhar seu banco de dados.</p><h3><strong>Passo 2: Exportando o banco do simulador</strong></h3><p>Agora que você já sabe onde o Core Data salva os arquivos, vamos <strong>automatizar a exportação com um script</strong>, bash. Assim, você pode copiar facilmente os arquivos .sqlite direto do simulador para uma pasta dentro do seu projeto Xcode (ex: DataBaseResources).</p><p><strong>📄 Crie um script chamado copiar_sqlite_para_DataBaseResources.sh</strong></p><pre>#!/bin/bash<br><br># CONFIGURAÇÃO — Altere conforme necessário<br>BUNDLE_ID=&quot;Gabriel.plantei&quot;<br>DEST_FOLDER=&quot;./DataBaseResources&quot;<br><br># PASSO 1 — Descobre o UDID do simulador atual<br>UDID=$(xcrun simctl list devices booted | grep -oE &quot;[A-F0-9\\-]{36}&quot;)<br><br>if [[ -z &quot;$UDID&quot; ]]; then<br>  echo &quot;❌ Nenhum simulador está rodando. Inicie o app no simulador antes de continuar.&quot;<br>  exit 1<br>fi<br><br>echo &quot;🟢 Simulador ativo: $UDID&quot;<br><br># PASSO 2 — Localiza o container de dados do app<br>APP_PATH=$(xcrun simctl get_app_container &quot;$UDID&quot; &quot;$BUNDLE_ID&quot; data)<br><br>if [[ ! -d &quot;$APP_PATH&quot; ]]; then<br>  echo &quot;❌ Caminho do app não encontrado. Verifique o bundle ID e se o app já foi executado no simulador.&quot;<br>  exit 1<br>fi<br><br># PASSO 3 — Define a pasta origem dos arquivos<br>SOURCE_PATH=&quot;$APP_PATH/Library/Application Support&quot;<br><br># PASSO 4 — Cria destino e copia os arquivos<br>mkdir -p &quot;$DEST_FOLDER&quot;<br><br>for EXT in sqlite sqlite-shm sqlite-wal; do<br>  cp &quot;$SOURCE_PATH/DataBase.$EXT&quot; &quot;$DEST_FOLDER/DataBase.$EXT&quot; &amp;&amp; echo &quot;✅ Copiado: DataBase.$EXT&quot;<br>done<br><br>echo &quot;🎉 Banco exportado com sucesso para $DEST_FOLDER&quot;</pre><p><strong>Obs:</strong> lembre-se de colocar o BUNDLE_ID correto do seu projeto e rodar o simulador do seu projeto.</p><h4>🛠️ Como rodar:</h4><p><strong>0. No terminal entre na pasta do seu projeto</strong></p><ol><li><strong>Dê permissão de execução:</strong></li></ol><pre>chmod +x copiar_sqlite_para_DataBaseResources.sh</pre><p>2. Execute o script</p><pre>./copiar_sqlite_para_DataBaseResources.sh</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*D1olWrHcP-WIOqQsqUCmWA.png" /><figcaption>Exemplo prático no terminal</figcaption></figure><p>Esse script vai:</p><ul><li>Identificar o simulador rodando;</li><li>Localizar o diretório de dados da aplicação;</li><li>Copiar os 3 arquivos essenciais do Core Data;</li><li>Colocá-los na pasta DataBaseResources, <strong>dentro do seu projeto</strong>.</li></ul><p>Caso você não queira usar a pasta DataBaseResources, basta copiar para dentro de outra pasta, no meu caso, usei a Model. Agora:</p><ol><li>Agora vá em <strong>Add Files to “projeto”</strong>,</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/732/1*NxDfo-ZEwh_LQEl-mvBX6Q.png" /></figure><p>2. Selecione esses 3 arquivos</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iDwbQUlqZ8KuBlYh8SAiSw.png" /></figure><p>3. Agora adicione ao seu projeto</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HGgwlr-ACX6RxLnXene9gQ.png" /></figure><blockquote><em>💡 Não se esqueça de </em><strong><em>adicionar os arquivos ao Xcode</em></strong><em>, marcando a opção </em>“References files in place”<em> e incluindo no </em><strong>target principal do app</strong><em>.</em></blockquote><h3><strong>Passo 3: configurando o carregamento automático do BD</strong></h3><p>Depois de fazer os passos anteriores, vamos configurar o carregamento automático.</p><h4>O que vamos fazer:</h4><ul><li>Verificar se já existe um banco na pasta Application Support;</li><li>Se não existir, copiar os arquivos .sqlite que estão no bundle para essa pasta;</li><li>Somente após isso inicializar o Core Data.</li></ul><h4>Por que isso é importante?</h4><p>Se o BD for inicializado antes da cópia, o Core Data cria um novo banco vazio — e <strong>ignora</strong> os arquivos que você incluiu no projeto. Por isso, a ordem correta é essencial.</p><p>OBS: Nesse passo irei explicar detalhadamente a lógica do código, caso você não esteja com muito tempo, <strong>pule para o passo 4, </strong>onde tem o código final.</p><p>Caso você tenha ficado, vamos lá! Para fins didáticos, irei assumir que esteja iniciando em CoreData.</p><p><strong>📦 Declaração de propriedades</strong></p><pre>static let shared = DataBase()<br>let container: NSPersistentContainer<br>var viewContext: NSManagedObjectContext</pre><ul><li>static let shared: cria uma instância única da classe DataBase (Singleton)DataBase.shared, para ser usada em qualquer parte do app.</li><li>let container: armazena o NSPersistentContainer. responsável por carregar e salvar o modelo.</li><li>var viewContext: acesso rápido para leitura e gravação</li></ul><p><strong>🔒 Inicializando o construtor</strong></p><pre>private init() {<br>    Self.preloadDatabaseIfNeeded()<br>    container = NSPersistentContainer(name: &quot;DataBase&quot;)<br>    container.loadPersistentStores { _, error in<br>        if let error = error as NSError? {<br>            fatalError(&quot;❌ Erro ao carregar o Core Data: \(error), \(error.userInfo)&quot;)<br>        } else {<br>            print(&quot;✅ Core Data carregado com sucesso.&quot;)<br>        }<br>    }<br>}</pre><ul><li>self.preloadDatabaseIfNeeded(): garante que o banco pré-populado seja copiado antes de abrir.</li><li>NSPersistentContainer(name: &quot;DataBase)&quot;: cria o container com base no modelo .xcdatamodeld.</li><li>.loadPersistentStores: carrega o banco, se falhar, encerra com erro; se funcionar, exibe sucesso no log.</li></ul><p>👉 <strong>Função de pré-carregamento</strong></p><pre> static func preloadDatabaseIfNeeded() {<br>        let fileManager = FileManager.default<br><br>        let supportURL = try! fileManager.url(/service/for: .applicationSupportDirectory,<br>                                              in: .userDomainMask,<br>                                              appropriateFor: nil,<br>                                              create: true)<br><br>        let sqlitePath = supportURL.appendingPathComponent(&quot;DataBase.sqlite&quot;)<br><br>        if !fileManager.fileExists(atPath: sqlitePath.path) {<br>            print(&quot;📦 Copiando banco pré-populado...&quot;)<br><br>            guard let sqliteSrc = Bundle.main.url(/service/forresource: &quot;DataBase&quot;, withExtension: &quot;sqlite&quot;),<br>                  let shmSrc = Bundle.main.url(/service/forresource: &quot;DataBase&quot;, withExtension: &quot;sqlite-shm&quot;),<br>                  let walSrc = Bundle.main.url(/service/forresource: &quot;DataBase&quot;, withExtension: &quot;sqlite-wal&quot;) else {<br>                print(&quot;❌ Arquivos .sqlite não encontrados no bundle.&quot;)<br>                return<br>            }<br><br>            do {<br>                try fileManager.copyItem(at: sqliteSrc, to: sqlitePath)<br>                try fileManager.copyItem(at: shmSrc, to: supportURL.appendingPathComponent(&quot;DataBase.sqlite-shm&quot;))<br>                try fileManager.copyItem(at: walSrc, to: supportURL.appendingPathComponent(&quot;DataBase.sqlite-wal&quot;))<br><br>                print(&quot;✅ Banco copiado com sucesso para Application Support.&quot;)<br>            } catch {<br>                print(&quot;❌ Erro ao copiar banco: \(error)&quot;)<br>            }<br>        } else {<br>            print(&quot;✔️ Banco já existe. Nenhuma cópia é necessária.&quot;)<br>        }<br>    }</pre><ul><li><strong>Verifica</strong> se o banco de dados já existe no Application Support.</li><li><strong>Se não existir</strong>, copia os arquivos .sqlite, .sqlite-shm e .sqlite-wal do bundle.</li><li><strong>Garante</strong> que o app use um banco pré-populado já na primeira execução.</li><li><strong>Evita cópia duplicada</strong> se o banco já estiver lá.</li></ul><p><strong>💾 Salvando o Contexto</strong></p><pre>func saveContext() {<br>    let context = container.viewContext<br>    if context.hasChanges {<br>        do {<br>            try context.save()<br>            print(&quot;✅ Alterações salvas no banco.&quot;)<br>        } catch {<br>            print(&quot;❌ Erro ao salvar contexto: \(error)&quot;)<br>        }<br>    }<br>}</pre><ul><li>Verifica se há alterações no viewContext (dados em memória).</li><li>Se houver, tenta salvar essas alterações no banco SQLite.</li><li>Exibe logs indicando sucesso ou erro durante a operação.</li></ul><p><strong>Obs: É essencial chamar essa função após criar, editar ou deletar dados no Core Data para garantir persistência.</strong></p><h3><strong>Passo 4: Código final</strong></h3><pre>import Foundation<br>import CoreData<br><br>final class DataBase {<br><br>    static let shared = DataBase()<br>    let container: NSPersistentContainer<br><br>    var viewContext: NSManagedObjectContext {<br>        return self.container.viewContext<br>    }<br><br>    private init() {<br>        // ⚠️ Copiar banco antes de carregar o Persistent Store<br>        Self.preloadDatabaseIfNeeded()<br><br>        container = NSPersistentContainer(name: &quot;DataBase&quot;)<br>        container.loadPersistentStores { _, error in<br>            if let error = error as NSError? {<br>                fatalError(&quot;❌ Erro ao carregar o Core Data: \(error), \(error.userInfo)&quot;)<br>            } else {<br>                print(&quot;✅ Core Data carregado com sucesso.&quot;)<br>            }<br>        }<br>    }<br><br>    static func preloadDatabaseIfNeeded() {<br>        let fileManager = FileManager.default<br><br>        let supportURL = try! fileManager.url(/service/for: .applicationSupportDirectory,<br>                                              in: .userDomainMask,<br>                                              appropriateFor: nil,<br>                                              create: true)<br><br>        let sqlitePath = supportURL.appendingPathComponent(&quot;DataBase.sqlite&quot;)<br><br>        if !fileManager.fileExists(atPath: sqlitePath.path) {<br>            print(&quot;📦 Copiando banco pré-populado...&quot;)<br><br>            guard let sqliteSrc = Bundle.main.url(/service/forresource: &quot;DataBase&quot;, withExtension: &quot;sqlite&quot;),<br>                  let shmSrc = Bundle.main.url(/service/forresource: &quot;DataBase&quot;, withExtension: &quot;sqlite-shm&quot;),<br>                  let walSrc = Bundle.main.url(/service/forresource: &quot;DataBase&quot;, withExtension: &quot;sqlite-wal&quot;) else {<br>                print(&quot;❌ Arquivos .sqlite não encontrados no bundle.&quot;)<br>                return<br>            }<br><br>            do {<br>                try fileManager.copyItem(at: sqliteSrc, to: sqlitePath)<br>                try fileManager.copyItem(at: shmSrc, to: supportURL.appendingPathComponent(&quot;DataBase.sqlite-shm&quot;))<br>                try fileManager.copyItem(at: walSrc, to: supportURL.appendingPathComponent(&quot;DataBase.sqlite-wal&quot;))<br><br>                print(&quot;✅ Banco copiado com sucesso para Application Support.&quot;)<br>            } catch {<br>                print(&quot;❌ Erro ao copiar banco: \(error)&quot;)<br>            }<br>        } else {<br>            print(&quot;✔️ Banco já existe. Nenhuma cópia necessária.&quot;)<br>        }<br>    }<br><br>    func saveContext() {<br>        let context = container.viewContext<br>        if context.hasChanges {<br>            do {<br>                try context.save()<br>                print(&quot;✅ Alterações salvas no banco.&quot;)<br>            } catch {<br>                print(&quot;❌ Erro ao salvar contexto: \(error)&quot;)<br>            }<br>        }<br>    }<br>}</pre><h3><strong>Passo 5: Testar</strong></h3><p>❌ Como meu <strong>app de testes já está populado</strong>, ao executar o app novamente, a cópia dos arquivos <strong>não será realizada</strong>. Isso acontece porque o arquivo .sqlite já existe na pasta Application Support.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GYffm1n2FqvFEKXl-NeJ7Q.png" /><figcaption>Nesse caso, será exibida no terminal do Xcode a mensagem que configuramos: <strong>“Banco já existe. Nenhuma cópia necessária.”</strong></figcaption></figure><p>Irei apagar o App do meu simulador para testar se a cópia está, de fato, funcionando corretamente. Veja que agora deu certo, dado que meu BD está vazio</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wd6w3AoxEsaVtvKbkPAu5w.png" /></figure><p><strong>(Obs: deve ter apenas uma versão do seu app no simulador)</strong></p><h3>Conclusão</h3><p>Dessa forma, conseguimos integrar um banco de dados SQLite já populado diretamente no projeto, garantindo persistência e consistência nos dados desde a primeira execução do app — <strong>ressalto que é ideal para times que compartilham o projeto iOS via Git ou precisam de dados de teste consistentes durante o desenvolvimento.</strong></p><p>Caso esteja enfrentando problemas com a cópia do banco, revise:</p><p>• Se os 3 arquivos (.sqlite, .sqlite-shm, .sqlite-wal) estão no bundle; além de estarem marcado no Target.</p><p>• Se o nome do NSPersistentContainer coincide com o nome do arquivo;</p><p>• Se o banco já existe em Application Support (neste caso, a cópia será ignorada).</p><p>⸻</p><p><strong>💡 Dica Final</strong></p><p>Se quiser testar novamente o carregamento do banco, basta apagar o app do simulador e executar o projeto outra vez. Assim, você valida que os dados realmente estão sendo copiados apenas na primeira inicialização.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f9d31ac32795" width="1" height="1" alt=""><hr><p><a href="https://medium.com/appledeveloperacademy-ufpe/guia-como-subir-um-app-ios-com-core-data-pr%C3%A9-populado-no-github-f9d31ac32795">Guia: Como Subir um App iOS com Core Data Pré-Populado no GitHub</a> was originally published in <a href="https://medium.com/appledeveloperacademy-ufpe">Apple Developer Academy | UFPE</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Desenvolvendo um Botão de Leitura de Texto com SwiftUI]]></title>
            <link>https://medium.com/appledeveloperacademy-ufpe/desenvolvendo-um-bot%C3%A3o-de-leitura-de-texto-com-swiftui-26c6a2f8f2ca?source=rss----b27283248e96---4</link>
            <guid isPermaLink="false">https://medium.com/p/26c6a2f8f2ca</guid>
            <category><![CDATA[apple-developer-academy]]></category>
            <category><![CDATA[botão]]></category>
            <category><![CDATA[ada]]></category>
            <category><![CDATA[idiomas]]></category>
            <category><![CDATA[xcode]]></category>
            <dc:creator><![CDATA[Naomi Lin]]></dc:creator>
            <pubDate>Wed, 25 Feb 2026 13:23:41 GMT</pubDate>
            <atom:updated>2026-02-25T13:25:13.137Z</atom:updated>
            <content:encoded><![CDATA[<h3>Desenvolvendo um Botão de Leitura de Texto (speech) com SwiftUI</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*8T_8EbDlhScINppHezsNrg.png" /><figcaption>Ícone do App &quot;Ciao, Italia&quot;.</figcaption></figure><p>No challenge da Ilhéu de Nova Descoberta, da Apple Developer Academy UFPE, cada grupo teve que fazer um app que explorasse suas paixões. No trio “<em>Ciao, Italia</em>”, tivemos que criar um app que envolvesse nossas três paixões: idioma (italiano), viagem e arte.</p><p>Dessa forma, chegamos a uma <em>Solution</em> <em>Concept</em>:</p><blockquote>“Um app que simula uma situação cotidiana e ensina expressões-chaves ao viajar”.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5y2bWmLt8xCgpY8IKGxGsw.png" /><figcaption>Behance: <a href="https://www.behance.net/gallery/202339635/Ciao-Italia-App">https://www.behance.net/gallery/202339635/Ciao-Italia-App</a></figcaption></figure><p>Um dos pontos mais interessantes trabalhados ao longo do desenvolvimento dele foi criar um botão de leitura de um texto predefinido no <em>SwiftUI</em>. Para fazê-lo, usamos de referência <a href="https://hoyelam.medium.com/how-to-let-ios-speak-to-you-9cb36d923692"><em>este</em></a> post no Medium para nos ajudar.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*d68ZkboSGwy0deGhHhzhaA.png" /></figure><h3>Como começar?</h3><pre>import AVFoundation<br><br>class SpeechManager {<br>    private let synthesizer = AVSpeechSynthesizer()<br>    <br>    func speak(_ text: String) {<br>        let utterance = AVSpeechUtterance(string: text)<br>        utterance.voice = AVSpeechSynthesisVoice(language: &quot;it-IT&quot;) // Italiano<br>        utterance.rate = 0.5<br>        synthesizer.speak(utterance)<br>    }<br>}</pre><p>Primeiramente, utilizamos o <em>import AVFoundation</em> para termos acesso ao AVSpeechSynthesizer, o que fornecerá uma variedade de controles para o áudio (síntese de fala).</p><p>Logo, criamos uma classe chamada <em>SpeechManager</em> com uma propriedade privada chamada <em>synthesizer</em> que inicializa a instância A<em>VSpeechSynthesizer </em>que é o que realizará a síntese de fala.</p><p>Depois disso, criamos uma uma função chamada <em>speak</em> que aceita uma <em>string</em> como parâmetro, é ela que vai converter o texto em fala. Nela criamos uma constante chamada <em>utterance</em> que terá a instância de <em>AVSpeechUtterance</em> com o texto que vai ser fornecido para configurar a maneira que a fala será sintetizada (a dicção).</p><p>Para configurar o idioma da fala, utilizamos <em>utterance.voice</em> com o <em>AVSpeechSynthesisVoice</em>. Nesse exemplo configuramos “<em>it-IT</em>” para que a linguagem seja italiano.</p><p>A <em>utterance.rate</em> é usada para configurar a velocidade da fala, o valor padrão é o 0.5 mas pode variar de 0 a 1.</p><p>E por último, chamamos o método <em>speak</em> do <em>synthesizer</em> com a <em>utterance</em> para iniciar a síntese da fala com as configurações que criamos.</p><h3>E agora, como criar um botão?</h3><pre>import SwiftUI<br>struct ButtonView: View {<br>    private let speechManager = SpeechManager()<br>    <br>    var body: some View {<br>        VStack {<br>            Button(action: {<br>                speechManager.speak(&quot;Ciao, come stai?&quot;)<br>            }) {<br>                ZStack {<br>                    Capsule()<br>                        .foregroundColor(.blue)<br>                        .frame(width: 140, height: 44)<br>                    Text(&quot;FALAR&quot;)<br>                        .foregroundColor(.white)<br>                }<br>            }<br>        }<br>    }<br>}</pre><p>Para criarmos um botão de leitura de texto, abrimos uma outra <em>view</em> que contém uma instância privada de <em>SpeechManager</em> chamada <em>speechManaager</em> que trará as funcionalidades da síntese de fala que foram definidas na classe <em>SpeechManager</em>.</p><p>Depois de estilizar o botão, colocamos em sua ação o método speak do speechManager e passamos uma string, no exemplo colocamos “Ciao, come stai?” para que seja falada ao ser pressionado o botão “FALAR”.</p><p>Espero que este tutorial tenha sido útil e que você tenha aprendido a criar um botão de leitura de texto em SwiftUI! Se você tiver alguma dúvida ou sugestão, sinta-se à vontade para deixar um comentário abaixo.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WoAiA_tnBCQwGJreRQsEyQ.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=26c6a2f8f2ca" width="1" height="1" alt=""><hr><p><a href="https://medium.com/appledeveloperacademy-ufpe/desenvolvendo-um-bot%C3%A3o-de-leitura-de-texto-com-swiftui-26c6a2f8f2ca">Desenvolvendo um Botão de Leitura de Texto com SwiftUI</a> was originally published in <a href="https://medium.com/appledeveloperacademy-ufpe">Apple Developer Academy | UFPE</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>