em defesa da caixinha de gorjetas
por que escolhi gorjetas voluntárias pro EXIF.ER, e como três botões viraram um pequeno projeto com StoreKit
Eu queria colocar um pequeno agradecimento no EXIF.ER. Três botões na tela de Informações e Créditos, com valores de um café, um lanche ou um almoço. Em algum momento, isso ganhou um observador de transações, detecção de duplicatas, uma migração de uma chave booleana e testes pro caso de a loja não retornar absolutamente nada. Tem uma quantidade respeitável de código por trás de comprar um croissant imaginário pra alguém.
Criei o EXIF.ER pros meus próprios backups de fotos. Ele renomeia fotos e vídeos pela data em que foram feitos, e a caixinha chegou na atualização 3.0. Uma gorjeta acrescenta um agradecimento e alguns corações na tela de Informações e Créditos. Todos os recursos de renomeação são gratuitos.
pagar pra terminar uma tarefa
Às vezes tenho um lote de arquivos pra organizar. Depois deixo o app de lado. Uma assinatura me daria mais uma coisa pra lembrar de cancelar depois de terminar a tarefa, o que seria uma melhoria meio estranha na minha rotina de backups.
Eu gostaria de receber pelo trabalho. Bugs sobrevivem à versão em que eu tinha certeza de ter encontrado todos, e acompanhar as mudanças do iOS toma tempo. Mas, quando penso em cobrar por esse app, acabo imaginando alguém com uma pasta parecida com a minha, querendo organizar as fotos e ir fazer outra coisa.
Não consegui me convencer a fazer um contador que interrompesse o lote na décima primeira foto até alguém pagar. Eu teria que escolher onde atrapalhar uma tarefa que passei todo esse tempo tentando deixar menos chata. Então o convite ficou em Informações e Créditos, onde a pessoa pode deixar uma gorjeta se o app tiver sido útil.
quando a loja não retorna nada
As gorjetas são produtos consumíveis, com os identificadores e nomes traduzidos em
TipTier. Ordeno os produtos recebidos pra manter café antes de lanche e almoço.
Pros preços, os botões usam o Product.displayPrice do StoreKit, já formatado
pra região da loja.
Desenhar os botões é simples depois que os produtos chegam. O carregamento também pode falhar,
ou a loja pode devolver uma lista vazia. O serviço TipJar acompanha isso com um
enum pequeno:
enum State: Equatable {
case loading
case unavailable
case ready([TipOffer])
}
Uma falha ou resposta vazia deixa na tela uma mensagem e um botão pra tentar de novo. Quando as
ofertas chegam, a interface recebe valores TipOffer com o texto e o emoji de cada
botão; os produtos do StoreKit ficam no serviço. Enquanto uma compra está aberta, os outros
botões de gorjeta ficam desativados.
Isso também me deu um jeito de preparar screenshots sem esperar pela loja. O harness de debug fornece ofertas, incluindo os preços, pras mesmas views usadas no app. Eu podia conferir os botões em português mesmo quando o StoreKit não respondia.
contar as gorjetas
O agradecimento precisa aparecer mesmo quando uma compra termina mais tarde. O EXIF.ER escuta Transaction.updates e encaminha as atualizações
pro mesmo método que recebe um resultado bem-sucedido de purchase(). Ele confere a
verificação, registra a gorjeta e chama finish().
Com dois caminhos de entrada, precisei evitar que a mesma transação fosse contada duas vezes. O método de registro guarda os identificadores que já recebeu naquela execução e ignora produtos desconhecidos e transações revogadas. Cada gorjeta aceita acrescenta um coração, até chegar a cinco.
A contagem fica no UserDefaults daquela instalação. Uma versão anterior guardava só
se a pessoa já tinha dado gorjeta, então há uma migração que transforma o antigo “sim” em um
coração.
Testo o método de registro com identificadores e flags simples. Envio o mesmo identificador duas vezes e confiro se acrescentou um coração; envio uma transação revogada e confiro se não acrescentou nenhum. Outros testes cobrem a contagem salva e a antiga chave booleana. No carregamento, um closure permite que os testes retornem uma lista vazia ou lancem um erro, depois tentem de novo. Esses testes exercitam meu código. Comprar uma gorjeta pela loja ainda precisa de uma verificação separada.
espaço pro nome
Três preços lado a lado ficam alinhados até um nome traduzido quebrar de linha e empurrar o
próprio preço pra baixo. O botão usa lineLimit(2, reservesSpace: true) pra reservar
duas linhas pra cada nome, mesmo quando só precisa de uma. Nos tamanhos de texto de
acessibilidade, AnyLayout empilha os botões na vertical e os nomes podem ocupar o
espaço de que precisarem.
Eu tinha pensado na caixinha como uma adição pequena ao app e acabei ajustando o espaço acima de um preço. É fácil dedicar menos cuidado a uma tela que eu mesmo quase não abro.
A versão 3.0 já está na App Store. Até 10 de setembro de 2026, ninguém deixou uma gorjeta. Ainda estou na esperança daquele café.