Oi, pessoal. Eu sou o Ayumu Kitabayashi, do Japão, e desenvolvo sozinho o minulo, um app de iPhone que tira da tela os Reels do Instagram, os Shorts do YouTube e o "Para você" do TikTok, mas deixa as DMs funcionando. O app é feito em Expo e React Native, então este texto é principalmente para quem vive nesse ecossistema e um dia pensou: "dá pra fazer um app que esconde só uma parte de outro app?".
A resposta curta é: não do jeito que você imagina. Mas existem três caminhos que funcionam, e a escolha entre eles define quase tudo no projeto, da polÃtica de privacidade até quanto trabalho de manutenção você vai ter toda semana.
Antes de tudo: o iOS não deixa você mexer no app dos outros
Se você vem do Android, talvez esteja pensando em serviços de acessibilidade, aqueles que leem a tela de outro app e reagem. No iOS isso simplesmente não existe para terceiros.
O que a Apple oferece é a API de Tempo de Uso (FamilyControls, ManagedSettings e DeviceActivity). Com ela você bloqueia um app inteiro, uma categoria inteira ou um domÃnio inteiro no Safari. Não existe parâmetro para "Instagram, menos a aba de Reels". É tudo ou nada.
Então o único lugar onde dá para esconder um pedaço é na versão web da rede social. Lá é HTML e CSS, e um seletor resolve. A pergunta que sobra é: quem desenha essa página?
- O Safari, aplicando as regras de um content blocker
- O Safari, rodando o script de uma extensão web
- O seu próprio app, dentro de um WKWebView
Caminho 1: content blocker do Safari
Um content blocker é uma lista de regras em JSON. Você entrega a lista, o Safari compila e aplica sozinho. O seu código nunca entra na página.
[{
"trigger": { "url-filter": "^https://www\\.youtube\\.com/" },
"action": { "type": "css-display-none", "selector": "a[href^='/shorts']" }
}]
O ponto forte é de arquitetura, não de performance: a extensão não consegue ler a página. Para um app que fica aberto em cima das suas conversas privadas, isso vale muito.
Agora, os limites que encontrei montando as regras:
-
Só existe
display: none. Não dá para mudar outra propriedade. Numa das redes, as abas da versão mobile eram posicionadas pela ordem; quando escondi uma, as vizinhas não se ajustaram e ficou um buraco. Esconder o contêiner deixou um retângulo cinza no lugar. A solução era desativar a aba sem removê-la, e isso pede JavaScript. -
O
url-filteré um regex reduzido. Não tem alternância com|, nem\d, nem quantificador{n}. Duas rotas viram duas regras. -
O formato não perdoa. Uma chave fora da especificação pode fazer o Safari descartar a lista inteira, sem aviso nenhum na página. Por isso guardo os metadados (de qual rede veio cada regra) num arquivo paralelo, na mesma ordem, e a build falha se aparecer qualquer chave além de
triggereaction.
Caminho 2: extensão web do Safari
Com uma extensão web você injeta CSS e JavaScript de verdade. Dá para observar o DOM, desativar botões, redirecionar /reels/ para a caixa de mensagens. Tudo o que o content blocker não faz.
O preço: o script lê a página, inclusive as DMs, se a pessoa estiver nelas. O Safari pede permissão por site e avisa que a extensão pode ver o conteúdo. Para quem instalou um app justamente para usar o celular com mais calma, é um primeiro contato pesado.
A regra que eu seguiria nesse cenário: tudo que cabe num seletor fica no content blocker, e só o que exige lógica vai para o script. Quanto mais coisa no script, menos vale o argumento de privacidade.
O problema que os dois caminhos do Safari têm em comum
Os dois só funcionam dentro do Safari. Só que o hábito que a gente quer quebrar é o polegar indo direto no Ãcone do Instagram, e esse Ãcone abre o app nativo, onde extensão de Safari não chega.
Ou seja, a pessoa teria que lembrar de abrir instagram.com no navegador justo no momento em que ela menos lembra de qualquer coisa. No celular, é aà que o plano desmorona.
Caminho 3: o seu app, com react-native-webview
É o que o minulo faz. O app abre a versão web oficial de cada rede num WKWebView (via react-native-webview) e injeta o próprio CSS e JS. O login acontece na página de login da própria rede, dentro desse WebView.
O que faz isso funcionar no iPhone é juntar com o Tempo de Uso: o minulo bloqueia o Instagram nativo, e a tela de bloqueio oferece um botão que leva para o minulo. O hábito do Ãcone continua, mas termina num lugar sem Reels.
Algumas coisas que aprendi no caminho, pensando em quem está no Expo:
Duas injeções, em momentos diferentes. O CSS entra por injectedJavaScriptBeforeContentLoaded, antes da primeira pintura. Se entrar depois, o Reel pisca na tela antes de sumir. O comportamento entra por injectedJavaScript com um MutationObserver, porque essas páginas continuam adicionando elementos muito depois do carregamento.
SPA não recarrega. A injeção roda uma vez por documento, mas a rede troca de rota sem recarregar. Uma regra que vale só em /explore/ precisa de um script que observe a URL e troque um <style> especÃfico por conta própria.
Seletor quebrado não dá erro. Quando a rede renomeia uma classe, o CSS simplesmente para de casar. Eu conto quantos elementos cada regra encontra e trato como quebrada a regra que fica em zero.
O código injetado roda no mundo da página. O JavaScript da própria rede pode chamar window.ReactNativeWebView.postMessage tão bem quanto o seu. No lado do app, toda mensagem é suspeita: confiro um token gerado para aquela tela e uma lista fechada de tipos aceitos. É só a primeira camada.
User agent atualizado. Eu usava um user agent fixo, de um Safari antigo. Hoje o app informa a mesma versão de Safari do aparelho, para não chamar atenção à toa.
Tempo de Uso no Expo pede dev client. A parte de bloqueio usa a biblioteca react-native-device-activity e extensões nativas (ShieldConfiguration para a aparência da tela de bloqueio, ShieldAction para os botões). Isso não roda no Expo Go; é expo-dev-client e build nativa. E um detalhe que me custou tempo: de dentro da ShieldAction não dá para abrir o seu app direto pela URL. O pedido é ignorado sem erro. A saÃda foi disparar uma notificação local que a pessoa toca para entrar no minulo.
Uma fonte de regras, três saÃdas
Para não ficar preso a uma escolha, as regras vivem num único arquivo TypeScript. As que são só seletor podem virar JSON de content blocker, as que precisam de lógica podem virar script, e todas são injetadas no WebView do app. A mesma declaração (urlFilter, css, redirect) é lida pelas três saÃdas. Foi assim que um bug real ficou visÃvel: o app ignorava o urlFilter enquanto as outras saÃdas respeitavam, e uma regra escondia coisas em todas as páginas no iPhone.
Resumo
| Content blocker | Extensão web | App com WebView | |
|---|---|---|---|
| Lê a página | Não | Sim | Sim |
| Desativa e redireciona | Não | Sim | Sim |
| Funciona ao tocar no Ãcone do Instagram | Não | Não | Sim, com Tempo de Uso |
| Manutenção | Baixa | Média | Alta |
Se o alvo é desktop, comece pelo content blocker e só adicione script onde for inevitável. No iPhone, quem decide é o Ãcone: a solução tem que estar onde o polegar vai.
Se quiser ver como ficou: minulo. Esconder os Reels e os Shorts é grátis. Perguntas sobre Expo, WebView ou Tempo de Uso, deixa aqui nos comentários.










