artigo técnico · 11 de novembro de 2026 · swift · watchos · revisão · bugs · produto

O app que estava pronto e tinha seis bugs: a revisão do LeitorWatch tela por tela

Artigo técnico · 11 de novembro de 2026 · 11 min de leitura

Um app que compila, roda e passa nos testes ainda pode estar inutilizável. O

LeitorWatch é um leitor de livros para Apple

Watch que importa arquivos do iCloud e lê em dois modos: RSVP (uma palavra por vez

no centro da tela, como um letreiro) e TTS (o relógio lendo em voz alta). Ele

passava nesses três critérios antes de uma revisão tela por tela. A revisão encontrou

seis problemas, três deles invisíveis a olho nu. Cada achado vem na forma de sintoma,

mecanismo e correção.

A coroa que mudava o número, não a velocidade

Girar a coroa digital trocava o ritmo na etiqueta (300, 325, 350) e a leitura

continuava na mesma velocidade.

A variável exibida na tela e o timer que move as palavras eram estados separados. O

timer nascia com um intervalo calculado a partir do valor da coroa e não era informado

das mudanças seguintes.

A correção é uma observação da variável, que recria o timer com o novo intervalo:

.onChange(of: wpm) { _, _ in
    if isPlaying { startTimer() }   // o novo ritmo vale na hora
}

Botões invisíveis (dois achados, mesma raiz)

Os botões de voltar e avançar no leitor, e os A-/A+ de tamanho de fonte, apareciam

como manchas escuras sobre o fundo preto. Existiam, respondiam ao toque, e não eram

vistos.

Os dois casos usavam tinta translúcida: laranja a 15% de opacidade sobre preto vira

preto; azul a 15% de opacidade sobre preto vira preto. A borda continuava lá, só não

havia pixels suficientes para enxergá-la.

A correção é ícone com cor sólida e controle explícito de contraste, mantendo a área

de toque grande o bastante para o pulso:

Button(action: { goForwardWords(5) }) {
    Image(systemName: "goforward")
        .foregroundColor(.orange)              // cor sólida, não tinta translúcida
}
.buttonStyle(.plain)
.frame(width: 36, height: 36)
.contentShape(Rectangle())                     // a área de toque continua grande

Avançar com o áudio pausado começava a falar

No modo de leitura por voz, pular uma frase com a fala pausada iniciava a leitura.

Os dois botões de navegação chamavam a mesma rotina que reinicia a fala, escrita para

o caso comum (áudio tocando) e sem tratamento do estado pausado.

A correção separa as responsabilidades: navegar é sempre permitido, e tocar áudio só

quando o usuário pediu para tocar.

guard currentSentenceIndex < sentenceInfos.count - 1 else { return }
currentSentenceIndex += 1
if isPlaying { restartSpeech() } else { updateProgress() }

A biblioteca substituída pelos livros de exemplo

Em falhas de leitura do arquivo de dados, o app voltava a exibir três livros de

demonstração no lugar da biblioteca do usuário.

A abertura do arquivo usava um bloco try/catch único. Qualquer erro, fosse proteção

de arquivo com o relógio bloqueado ou arquivo momentaneamente indisponível, caía no

mesmo caminho do “primeiro uso”, que semeia os exemplos e regrava o arquivo. Falhar era

tolerável; sobrescrever os dados do usuário, não.

A correção separa os dois cenários no carregamento, e ajusta a proteção do arquivo para

completeFileProtectionUntilFirstUserAuthentication, porque um leitor precisa

funcionar assim que o pulso é levantado, não só após o primeiro desbloqueio do dia:

// arquivo NÃO existe = primeira execução → semeia os exemplos
guard FileManager.default.fileExists(atPath: libraryURL.path) else {
    saveLibrary(mockBooks); return mockBooks
}
// arquivo existe mas não pôde ser lido → preserva, reporta, não sobrescreve
do { return try JSONDecoder().decode([Book].self, from: Data(contentsOf: libraryURL)) }
catch { print("biblioteca preservada: \(error)"); return [] }

Identificador confundido com rótulo, e a barra que dominava a tela

Em livros com dois capítulos de mesmo título, a lista de capítulos misturava as

linhas. O id do capítulo era o próprio título, e identificador repetido quebra a

correspondência entre dados e linhas na tela. A correção passou a usar título somado à

posição no texto.

A barra de progresso ocupava altura equivalente a um botão nas três telas que a

usavam. O ProgressView padrão do watchOS desenha uma barra alta, pensada para telas

maiores, então a correção é uma barra fina de 4 pontos desenhada à mão, com fundo

translúcido e preenchimento proporcional.

Como a revisão foi feita

Sem testes automatizados de interface, o app foi executado no simulador do watchOS 26.5

e cada tela inspecionada por captura de tela. Para chegar a telas de profundidade sem

depender de cliques imprecisos no simulador de relógio, um seletor de tela por variável

de ambiente foi incluído temporariamente, corrigido o que era preciso, e as capturas

repetidas comparando o obtido com o esperado. O seletor saiu do código antes da

publicação.

O que ficou, verificável

  • build ok no watchOS 26.5, executado no simulador;

  • botões laterais e A-/A+ visíveis (antes: tinta invisível);

  • velocidade da coroa aplicada no momento da mudança;

  • navegação com áudio pausado não dispara a fala;

  • falha de leitura preserva a biblioteca do usuário;

  • quatro das cinco ações do livro visíveis de uma vez na tela de opções (antes: duas).

O código e o histórico das correções estão em

github.com/ranzouu/leitorwatch.

O que esse exercício muda na prática: uma revisão que se limita a rodar o build valida

o caminho fácil. Os defeitos que impedem o uso (contraste, velocidade que não responde,

dados que somem) aparecem quando alguém percorre as telas e compara o que está escrito

com o que acontece. Compilar é o começo da revisão, não o fim.

por Rayson Severiano Rodrigues, Fortaleza/CE, novembro de 2026.

← todos os artigos