Um dos primeiros tópicos que explorei foi o conceito de variáveis. Rapidamente, percebi que as variáveis são como recipientes para armazenar dados, permitindo-nos acessar e manipular informações de forma dinâmica em nossos aplicativos. Com Swift, a definição de uma variável é simples e intuitiva, usando a palavra-chave var. Aprendi a declarar variáveis e a atribuir valores a elas, tornando-me consciente de como essa habilidade seria crucial para o desenvolvimento posterior de aplicativos.
var str = "Olá, mundo!"
Em seguida, mergulhei no mundo dos tipos de dados simples, que são os blocos de construção essenciais para qualquer programador Swift. Aprendi sobre tipos como Int, Double, String e outros, que me permitiram representar números inteiros, números de ponto flutuante e texto. Compreender a distinção entre esses tipos e como usá-los corretamente se revelou fundamental para evitar erros e criar aplicativos robustos.
var população = 8_000_000
var pi = 3.141
Em seguida, mergulhei no mundo dos tipos de dados simples, que são os blocos de construção essenciais para qualquer programador Swift. Aprendi sobre tipos como Int, Double, String e outros, que me permitiram representar números inteiros, números de ponto flutuante e texto. Compreender a distinção entre esses tipos e como usá-los corretamente se revelou fundamental para evitar erros e criar aplicativos robustos.
var pontuacao = 85
var str = "Sua pontuação foi \(pontuacao)"
Arrays - estruturas de dados são como coleções ordenadas que nos permitem armazenar vários elementos do mesmo tipo em uma única variável. Aprendi a criar, acessar e manipular arrays, o que abriu um mundo de possibilidades para armazenar e organizar dados em meus aplicativos futuros.
let profissoes = ["Desenvolvedor Júnior", "Suporte Técnico", "Tech Lider", "Desenvolvedor iOS"]
Sets - que são conjuntos não ordenados de valores únicos. Compreendi como eles são úteis para armazenar informações em que a ordem não é importante, mas a unicidade dos elementos é crucial.
let cores = Set(["red", "green", "blue"])
Tuplas - permitiram combinar vários valores de tipos diferentes em uma única estrutura de dados. Aprender a criar e descompactar tuplas foi essencial para lidar com dados complexos em situações onde arrays e sets não eram suficientes. Isso abriu um leque de possibilidades para representar dados de forma estruturada e flexível.
var nome = (nome: "Marcel", sobrenome: "Leite de Farias")
Dictionary - relacionam chaves a valores, permitindo-me armazenar e recuperar informações de maneira eficiente. Compreender como criar, acessar e atualizar dicionários foi fundamental para gerenciar informações complexas e relacionadas em meus futuros projetos.
let sorvetes = [
"Marcel": "Chocolate",
"Eloah": "Vanilla"
]
Tipos padrão - de um dicionário caso ele não encontre o valor, e não queira retornar nil para o usuário.
sorvetes["João", default: "Nenhum"]
Enum - estruturas de dados que representam um conjunto de valores relacionados. Enums provaram ser essenciais para criar tipos de dados personalizados que são mais expressivos e seguros em relação a erros de programação.
enum Result {
case success
case failure
}
Enum associated values - além de armazenar um valor simples, as enums também podem armazenar valores associados anexados a cada caso. Isso permite anexar informações adicionais às suas enumerações para que elas possam representar dados com mais nuances.
enum Activity {
case bored
case running(destination: String)
case talking(topic: String)
case singing(volume: Int)
}
Operadores aritméticos - Operadores aritméticos em Swift são símbolos ou caracteres especiais usados para realizar operações matemáticas em valores numéricos. Adição (+): Usado para somar dois valores. Subtração (-): Utilizado para subtrair um valor de outro. Multiplicação (*): Usado para multiplicar dois valores. Divisão (/): Utilizado para dividir um valor por outro. Módulo (%): Calcula o resto da divisão entre dois valores inteiros. Incremento (++) e Decremento (--): Usados para aumentar ou diminuir o valor de uma variável em 1. ou (+=) (-+) (/=) (*=)
var quote = "The rain in Spain falls mainly on the "
quote += "Spaniards"
Condição If e Else - As estruturas de controle de fluxo if e else em Swift são utilizadas para criar ramificações condicionais em seu código, permitindo que seu programa tome decisões com base em condições específicas.
if firstCard + secondCard == 2 {
print("Aces – lucky!")
} else if firstCard + secondCard == 21 {
print("Blackjack!")
} else {
print("Regular cards")
}
Condição Switch case - A estrutura switch em Swift é uma poderosa ferramenta de controle de fluxo usada para avaliar uma expressão ou valor e executar um bloco de código com base em correspondências específicas.
switch weather {
case "rain":
print("Bring an umbrella")
case "snow":
print("Wrap up warm")
case "sunny":
print("Wear sunscreen")
default:
print("Enjoy your day!")
}
Condição Switch case com Range Operator - O operador de intervalo (... ou ..<) pode ser usado em conjunto com a estrutura switch para criar casos que correspondam a um intervalo de valores. O operador de intervalo fechado (...) é usado para criar um intervalo que inclui os valores de início e fim. Ele é útil quando você deseja verificar se um valor está dentro de um determinado intervalo. O operador de intervalo semifechado (..<) cria um intervalo que inclui o valor de início, mas exclui o valor final. Isso é útil quando você deseja criar intervalos que não incluam o valor máximo.
let score = 85
switch score {
case 0..<50:
print("You failed badly.")
case 50..<85:
print("You did OK.")
default:
print("You did great!")
}
For loop: Swift tem algumas maneiras de escrever loops, mas o mecanismo subjacente é o mesmo: executar algum código repetidamente até que uma condição seja avaliada como falsa. O loop mais comum em Swift é um forloop: ele percorrerá arrays e intervalos, e cada vez que o loop for executado, ele extrairá um item e o atribuirá a uma constante.
let count = 1...10
for number in count {
print("Number is \(number)")
}
Exemplo com matrizes:
let albums = ["Red", "1989", "Reputation"]
for album in albums {
print("\(album) is on Apple Music")
}
Podemos também retirar a constante caso não for utiliza-la:
print("Players gonna ")
for _ in 1...5 {
print("play")
}
While loop - forneça uma condição para verificar, e seu código de loop irá girar e girar até que a condição falhe.
var number = 1
while number <= 20 {
print(number)
number += 1
}
print("Ready or not, here I come!")
Repeat Loop - A terceira maneira de escrever loops não é muito usada, mas é tão simples de aprender que podemos abordá-la: é chamada de repeat loop e é idêntica a um while loop, exceto que a condição a ser verificada vem no final. Código vai ser inicializado pelo menos umas vez!
var number = 1
repeat {
print(number)
number += 1
} while number <= 20
print("Ready or not, here I come!")
Exiting loops - Você pode sair de um loop a qualquer momento usando a palavra-chave break.
while countDown >= 0 {
print(countDown)
if countDown == 4 {
print("I'm bored. Let's go now!")
break
}
countDown -= 1
}
Exiting multiple loops - Se você colocar um loop dentro de outro, ele será chamado de loop aninhado , e não é incomum querer sair do loop interno e do loop externo ao mesmo tempo. Conseguimos apelidar um looping para utilizar o break:
outerLoop: for i in 1...10 {
for j in 1...10 {
let product = i * j
print ("\(i) * \(j) is \(product)")
if product == 50 {
print("It's a bullseye!")
break outerLoop
}
}
}
Functions | func - Funções foram criadas para permitir a reutilização de blocos de código facilmente, ajudando nós, desenvolvedores, a não repetir diversas vezes o mesmo código. Existe uma técnica chamada composição de funções. Ao dividir seu trabalho em múltiplas pequenas funções, a composição de funções nos permite construir grandes funções combinando essas pequenas funções de várias maneiras, um pouco como peças de Lego.
func printAjuda() {
let mensagem = """
Bem vindo!
Este é um blog para documentar o desenvolvimento
pessoal no projeto - Hacking with Swift
"""
print(mensagem)
}
printAjuda()
Parâmetros - Parâmetros são aquilo que está entre os parênteses nos métodos e funções, sendo muitas vezes usado como sinônimo de argumento. Um parâmetro também pode ser descrito como um elemento ou característica que pode ser usado para estabelecer comparações entre pessoas, comportamentos, eventos, etc.
func quadrado(numero: Int) {
print(numero * numero)
}
quadrado(numero: 8)
Return - Podemos enviar informações para as funções pelos parâmetros como também podemos voltar com esses dados com o return. Utilizando -> e o (Tipo do dado retornado)
func quadrado(numero: Int) -> Int {
return numero * numero
}
let resultado = quadrado(numero: 8)
print(resultado)
Parameter labels - Swift nos permite fornecer dois nomes para cada parâmetro: um para ser usado externamente ao chamar a função e outro para ser usado internamente dentro da função.
func saudacoes(to name: String) {
print("Olá, \(name)!")
}
saudacoes(to: "Marcel")
Omitindo parâmetros - Swift nos permite omitir labels dos parâmetros passados na função. Você pode obter esse mesmo comportamento em suas próprias funções usando um sublinhado, _, para o nome do seu parâmetro externo.
func saudacoes(_ person: String) {
print("Olá, \(person)!")
}
saudacoes("Marcel")
Parâmetros Padrões - São parâmetros que já nascem com um valor padrão na hora da estruturação da função. Exemplo: 'amigavel: Bool = true'
func saudacoes(_ person: String, amigavel: Bool = true) {
if amigavel == true {
print("Olá, \(person)!")
} else {
print("Ah não, \(person) de novo...")
}
}
saudacoes("Marcel")
saudacoes("Marcel", amigavel: false)
Variadic functions - São funções que aceitam qualquer número de parâmetros do mesmo tipo. Você pode tornar qualquer parâmetro variável escrevendo ...após seu tipo.
func quadrado(numeros: Int...) {
for numero in numeros {
print("\(numero) ao quadrado é \(numero * numero)")
}
}
quadrado(numeros: 1, 2, 3, 4, 5)
Throwing functions - Throwing functions em Swift são aquelas que são capazes de encontrar erros que não conseguem ou não querem lidar. Isso não significa que cometerão erros, apenas que é possível que consigam. Como resultado, o Swift garantirá que tenhamos cuidado ao usá-los, para que quaisquer erros que ocorram sejam resolvidos. (Está função foi escrita em inglês para semelhar ao desenvolvimento real).
enum PasswordError: Error {
case obvious
}
func checkPassword(_ password: String) throws -> Bool {
if password == "password" {
throw PasswordError.obvious
}
return true
}
Utilização de Throwing functions:
do {
try checkPassword("password")
print("That password is good!")
} catch {
print("You can't use that password.")
}
Parâmetro inout Você pode passar um ou mais parâmetros como inout, o que significa que eles podem ser alterados dentro da sua função, e essas alterações refletem no valor original fora da função.
func doubleInPlace(number: inout Int) {
number *= 2
}
var myNum = 10
doubleInPlace(number: &myNum)
Closures básicos Em Swift podemos utilizar funções como qualquer outro tipo, strings e inteiros. Isso significa que você pode criar uma função e atribuí-la a uma variável, chamar essa função usando essa variável e até mesmo passar essa função para outras funções como parâmetros.
let driving = {
print("I'm driving in my car")
}
Podemos chamar essa função assim:
driving()
Closures com parâmetros: Para fazer uma closure aceitar parâmetros, liste-os entre parênteses logo após a chave de abertura e escreva in para que o Swift saiba que o corpo principal do fechamento está iniciando.
let driving = { (place: String) in
print("I'm going to \(place) in my car")
}
Não utilizamos labels para os parâmetros! Usamos assim:
driving("Joinville")
Retornando valores com Closure: Swift permite que uma closue retorne valores, vamos fazer-la retornar uma String, então precisamos usar -> String antes do in e, em seguida, usar return como uma função normal
let drivingWithReturn = { (place: String) -> String in
return "I'm going to \(place) in my car"
}
Executamos desse jeito:
let message = drivingWithReturn("Joinville")
print(message)
Closure como parâmetro: Se quiséssemos passar essa closure para uma função para que possa ser executado dentro dessa função, especificaríamos o tipo de parâmetro como () -> Void. Isso significa “não aceita parâmetros e retorna Void” – a maneira de Swift dizer “nada”.
let driving = {
print("I'm driving in my car")
}
func travel(action: () -> Void) {
print("I'm getting ready to go.")
action()
print("I arrived!")
}
E utilizamos assim:
travel(action: driving)
Chama de Closure simplificada:
func animate(duration: Double, animations: () -> Void) {
print("Starting a \(duration) second animation…")
animations()
}
animate(duration: 3) {
print("Fade out the image")
}
Closures passadas como parâmetro com parâmetros: Estamos usando () -> Void o significado de “não aceita parâmetros e não retorna nada”, mas você pode prosseguir e preencher o campo () com os tipos de quaisquer parâmetros que seu fechamento deve aceitar.
func travel(action: (String) -> Void) {
print("I'm getting ready to go.")
action("Joinville")
print("I arrived!")
}
Chamamos desse jeito:
travel { (place: String) in
print("I'm going to \(place) in my car")
}
Closures passadas como parâmetro com parâmetros e com retorno de dados:
func travel(action: (String) -> String) {
print("I'm getting ready to go.")
let description = action("Joinville")
print(description)
print("I arrived!")
}
Chamamos desse jeito:
travel { (place: String) -> String in
return "I'm going to \(place) in my car"
}
Podemos abreviar os parâmetros, segue 3 exemplos abreviados:
travel { place -> String in
return "I'm going to \(place) in my car"
}
Abreviando novamente:
travel { place in
return "I'm going to \(place) in my car"
}
Novamente: Swift tem uma sintaxe abreviada que permite ir ainda mais curto. Swift fornece nomes automáticos para os parâmetros da closure. Eles são nomeados com um cifrão e, em seguida, um número contado a partir de 0.
travel {
"I'm going to \($0) in my car"
}
Structs: As structs em Swift são poderosas e possuem muitos recursos. Além disso, é mais seguro quando há a passagem por valor, em vez de passagem por referência (já que nesse caso, todas as instâncias estão conectadas entre si). São utilizadas para definir propriedades para armazenar dados. Definir métodos para prover funcionalidades. Definir subscripts para prover acessos de seus dados usando a sintaxe dos subscripts.
struct Sport {
var name: String
}
Precisamos instânciar uma struct para utiliza-la
var tennis = Sport(name: "Tennis")
print(tennis.name)
Propriedade computada: Uma propriedade computada executa o código para receber seu valor. Notem: olympicStatus
struct Sport {
var name: String
var isOlympicSport: Bool
var olympicStatus: String {
if isOlympicSport {
return "\(name) is an Olympic sport"
} else {
return "\(name) is not an Olympic sport"
}
}
}
Observador de propriedade: Os observadores de propriedade permitem executar código antes ou depois de qualquer alteração de propriedade.
struct Progress {
var task: String
var amount: Int {
didSet {
print("\(task) is now \(amount)% complete")
}
}
}
Methods: As estruturas podem ter funções dentro delas, e essas funções podem usar as propriedades da estrutura conforme necessário. Funções dentro de structs são chamadas de métodos , mas ainda usam a mesma func
struct City {
var population: Int
func collectTaxes() -> Int {
return population * 1000
}
}
Podemos utilizar desse jeito:
let london = City(population: 9_000_000)
london.collectTaxes()
Mutanting methods: Para alterarmos valores de atributos de uma struct precisamos utilizar a nomenclatura mutanting antes de iniciar nossa função
struct Person {
var name: String
mutating func makeAnonymous() {
name = "Anonymous"
}
}
var person = Person(name: "Marcel")
person.makeAnonymous()
Propriedades e Metodos da String: Strings são structs – elas têm seus próprios métodos e propriedades que podemos usar para consultar e manipular a string. Alguns exemplos: count, hasPrefix, uppercased, sorted
let string = "iOS Develper"
print(string.count)
print(string.hasPrefix("iOS"))
print(string.uppercased())
print(string.sorted())
Propriedades e Metodos do Array: Array também como a String é uma structs – elas têm seus próprios métodos e propriedades que podemos usar para consultar e manipular um array. Alguns exemplos: count, append, firstIndex, sorted, remove
var toys = ["Woody"]
print(toys.count)
toys.append("Buzz")
toys.firstIndex(of: "Buzz")
print(toys.sorted())
toys.remove(at: 0)
Inicializadores: Um inicializador nada mais é que uma forma de já te devolver o objeto com alguns valores setados. Mas uma coisa muito importante é, o inicializador só vai acessar os membros que forneçam acesso "externo" à struct, como propriedades public por exemplo.
struct User {
var username: String
init() {
username = "Anonymous"
print("Creating a new user!")
}
}
Referência a instancia: Em Swift, Dentro dos métodos você obtém uma constante especial chamada self, que aponta para qualquer instância da estrutura que esteja sendo usada no momento. Esse self valor é particularmente útil quando você cria inicializadores que possuem os mesmos nomes de parâmetros da sua propriedade.
struct Person {
var name: String
init(name: String) {
print("\(name) was born!")
self.name = name
}
}
Lazy properties | Propriedades lentas: Como otimização de desempenho, o Swift permite criar algumas propriedades somente quando elas são necessárias. (Melhorar o desempenho, apenas utilizada quando chamada ou atribuida valor.)
struct FamilyTree {
init() {
print("Creating family tree!")
}
}
Métodos e Propriedades estáticas: Podemos criar propriedades ou metodos estáticos para serem utilizados sem precisar instancia-los
struct Student {
static var classSize = 0
var name: String
init(name: String) {
self.name = name
Student.classSize += 1
}
}
print(Student.classSize)
Controle de acesso: O controle de acesso permite restringir qual código pode usar propriedades e métodos.
struct Person {
private var id: String
init(id: String) {
self.id = id
}
}
Classes: Classes são semelhantes às structs, permitem criar propriedades e métodos, mas existem diferenças que as diferenciam.
class Dog {
var name: String
var breed: String
init(name: String, breed: String) {
self.name = name
self.breed = breed
}
}
let poppy = Dog(name: "Poppy", breed: "Poodle")
Herança: Diferença entre classes e structs é que você pode criar uma classe baseada em uma classe existente – ela herda todas as propriedades e métodos da classe original e pode adicionar seus próprios métodos por cima.
class Dog {
var name: String
var breed: String
func makeNoise() {
print("Woof!")
}
init(name: String, breed: String) {
self.name = name
self.breed = breed
}
}
class Poodle: Dog {
}
class Poodle: Dog {
init(name: String) {
super.init(name: name, breed: "Poodle")
}
}
Overriding methods: Podemos sobrescrever os métodos herdados das classes. Utilizando override
class Poodle: Dog {
override func makeNoise() {
print("Yip!")
}
}
Final: Declaramos final no inicio da construção da classe para que nenhuma outra classe consiga herda-la
final class Dog {
var name: String
var breed: String
init(name: String, breed: String) {
self.name = name
self.breed = breed
}
}
Struct x Class: Podemos falar que uma Classe é do tipo referência e as Structs do tipo valor. Quando você copia uma estrutura, tanto o original quanto a cópia são coisas diferentes, alterar um não alterará o outro. Quando você copia uma classe, tanto o original quanto a cópia apontam para a mesma coisa, portanto, alterar um altera o outro.
class Singer {
var name = "Taylor Swift"
}
var singer = Singer()
print(singer.name)
var singerCopy = singer
singerCopy.name = "Justin Bieber"
Deinitializers: Código que é executado quando uma instância de uma classe é destruída.
class Person {
var name = "John Doe"
init() {
print("\(name) is alive!")
}
func printGreeting() {
print("Hello, I'm \(name)")
}
deinit {
print("\(name) is no more!")
}
}
Mutability: A diferença final entre classes e structs é a maneira como elas lidam com constantes. Se você tiver uma estrutura constante com uma propriedade variável, essa propriedade não poderá ser alterada porque a estrutura em si é constante.
class Singer {
var name = "Taylor Swift"
}
let taylor = Singer()
taylor.name = "Ed Sheeran"
print(taylor.name)
Protocols: Protocolos são uma forma de descrever quais propriedades e métodos algo deve ter. É um paradigma de desenvolvimento de software que permite a criação de abstrações de comportamentos esperados de um determinado tipo de objeto.
protocol Identifiable {
var id: String { get set }
}
struct User: Identifiable {
var id: String
}
func displayID(thing: Identifiable) {
print("My ID is \(thing.id)")
}
Herança de protocol: Podemos fazer heranças entre protocolos, você pode herdar vários protocolos ao mesmo tempo antes de adicionar suas próprias alterações.
protocol Payable {
func calculateWages() -> Int
}
protocol NeedsTraining {
func study()
}
protocol HasVacation {
func takeVacation(days: Int)
}
protocol Employee: Payable, NeedsTraining, HasVacation { }
Extensions: As extensões permitem adicionar métodos a tipos existentes, para fazê-los fazer coisas para as quais não foram originalmente projetados. Muito utilizada em Swift .
extension Int {
func squared() -> Int {
return self * self
}
}
Protocol extensions: Os protocolos permitem descrever quais métodos algo deveria ter, mas não fornecem o código interno. As extensões permitem que você forneça o código dentro de seus métodos, mas afetam apenas um tipo de dados
protocol Identifiable {
var id: String { get set }
func identify()
}
extension Identifiable {
func identify() {
print("My ID is \(id).")
}
}
struct User: Identifiable {
var id: String
}
let twostraws = User(id: "twostraws")
twostraws.identify()
Programação orientada a Protocolos - POP: As extensões de protocolo podem fornecer implementações padrão para nossos próprios métodos de protocolo.
protocol Identifiable {
var id: String { get set }
func identify()
}
extension Identifiable {
func identify() {
print("My ID is \(id).")
}
}
struct User: Identifiable {
var id: String
}
let twostraws = User(id: "twostraws")
twostraws.identify()
Optionals: Uma variável opcional pode, ou não, conter um valor. Caso não haja um valor, ela será nil, ou seja, nulo (não existe um valor). Se existe um valor, a variável se iguala a esse valor. (Utilizamos o ? para informar que a variavel ou método é opcional)
var age: Int? = nil
Unwrapping optionals: Utilizando a técnica de desembrulho, unwrapp, para termos certeza que aquela variavel ou método contém algum valor. Valores vázios causam erros criticos em tempo de compilação. Uma maneira comum de desembrulhar opcionais é com if let, sintaxe que desembrulha com uma condição.
if let unwrapped = name {
print("\(unwrapped.count) letters")
} else {
print("Missing name.")
}
Unwrapping com guard: Outra opção para realizar o unwrapping é o guard let, guard let irá desembrulhar um opcional para você, mas se encontrar nil dentro dele, espera que você saia da função, loop ou condição em que o usou.
func greet(_ name: String?) {
guard let unwrapped = name else {
print("You didn't provide a name!")
return
}
print("Hello, \(unwrapped)!")
}
Force unwrapping: o Swift permite forçar o desembrulhamento do opcional: converta-o de um tipo opcional para um tipo não opcional. (Caso você utilize o force unwrapping e a variavel esteja vazia irá causar erros criticos no seu app).
let num = Int(str)!
Unwrapping simplificado: Temos outra maneira de unwrapping mais simplificada, como nesse exemplo:
func username(for id: Int) -> String? {
if id == 1 {
return "Taylor Swift"
} else {
return nil
}
}
let user = username(for: 15) ?? "Anonymous"
Inicializadores falhos: Este é um inicializador falível : um inicializador que pode funcionar ou não. Você pode escrevê-los em suas próprias estruturas e classes usando init?()em vez de init()e retornar nilse algo der errado.
struct Person {
var id: String
init?(id: String) {
if id.count == 9 {
self.id = id
} else {
return nil
}
}
}
Typecastig: A conversão de tipo em Swift é implementada com os operadores is e as . Esses dois operadores fornecem uma maneira simples e expressiva de verificar o tipo de um valor ou converter um valor em um tipo diferente.
for pet in pets {
if let dog = pet as? Dog {
dog.makeNoise()
}
}
Starting iOS - Projeto 1
Dando inico ao desenvolvimento de aplicativos reais, nosso primeiro projeto irá listar imagens para o usuário em uma tabela.
Preparamos o ambiente de desenvolvimento, criação de pastas e incluimos fotos dentro do projeto já existentes no repositório projeto1 -> Content - hackingwithswift no Github
Incluimos várias imagens da Administração Oceânica e Atmosférica Nacional (NOAA), seus nomes começam com nssl Ex.: nssliOS.png
Iniciamos pelo arquivo ViewController, com o metodo padrão viewDidLoad
ViewDidLoad: o viewDidLoad(), método é chamado quando a tela é carregada e está pronta para ser customizada
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
}
Antes vamos instanciar nosso array de fotos no começo do nosso código, encima do metodo viewDidLoad()
var pictures = [String]()
Depois declaramos o FileManager, um tipo de dado que nos permite trabalhar com o sistema de arquivos e, no nosso caso, procurar essas fotos/arquivos
let fm = FileManager.default
let path = Bundle.main.resourcePath!
let items = try! fm.contentsOfDirectory(atPath: path)
for item in items {
if item.hasPrefix("nssl") {
pictures.append(item)
}
}
Logo em seguida começamos a estilizar o nosso aplicativo pelo Interface Builder com storyboard, incluimos uma NavigationController e uma TableViewController, colocamos um identificador na nossa TableViewCell para conseguirmos manipular na ViewController e mudamos a tipagem da ViewController para UITableViewController para utilizarmos como uma tabela.
Desenvolvemos em seguida dois metodos herdados da UITableViewController, tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell e tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int. Resumindo numberOfRowsInSection é a quantidade de linhas ou sessões que a tabela precisa ter, o Swift necessita dessa informação. E o cellForRowAt é a manipulação da célula, incluindo dados, etc.
tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int Nesse metodo necessita o retorno de um inteiro para saber quantas linhas irá realizar. Utilizamos o metodo count em nosso array de pictures.
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return pictures.count
}
tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell Desse jeito conseguimos colocar os valores do array dentro de cada célula, no metodo dequeueReusableCell da tableView, o parâmetro withIdentifier é o nosso identifier que colocamos em nossa TableViewCell lá no storyboard.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Picture", for: indexPath)
cell.textLabel?.text = pictures[indexPath.row]
return cell
}
Starting iOS - Projeto 2
Nosso próximo objetivo é projetar uma nova tela que será mostrada quando o usuário tocar em qualquer linha. Faremos com que ele mostre a imagem selecionada em tela cheia e ela deslizará automaticamente quando uma imagem for tocada.
Primeiro, precisamos criar um novo código que hospedará esta tela de detalhes. Segundo, precisamos desenhar a interface do usuário para esta tela dentro do Interface Builder.
Crie um arquivo Cocoa Touch Class nomeie ele para DetailViewController e para subclass, UIViewController, retire "Criar também arquivo XIB"
Abra a biblioteca de objetos e encontre “View Controller”. Arraste-o para o espaço à direita da sua ViewController existente.
Precisamos colocar um identificador nessa nova ViewController, vá para o inspetor de identidade pressionando Cmd+Alt+3 ou usando o menu. Agora digite “Detalhe” onde diz “ID do Storyboard”. É isso: agora podemos nos referir a essa ViewController como “Detalhe” no código. Clique na seta ao lado da caixa Classe e selecione “DetailViewController” para que nossa interface de usuário esteja conectada ao novo código que criamos anteriormente.
Abra novamente a sua biblioteca UI e arraste uma UIImageView dentro dessa nova ViewController que acabou de realizar. Pode esticar a UIImageView Até todas as extremidades do dispositivo.
Precisamos colocar algumas constraints na nossa UIImageView, para exercitarmos o Auto Layout, responsividade para o nosso aplicativo ser utilizado em diversos dispositivos Apple sem quebra de elementos. Por agora iremos utilizar o autolayout desse jeito: mas a maneira mais fácil agora é selecionar a visualização da imagem, ir ao menu Editor e escolher > Resolver problemas de layout automático > Redefinir para restrições sugeridas.
Regras Auto Layout:
Suas regras de layout devem estar completas. Ou seja, você não pode especificar apenas uma posição X para algo, você também deve especificar uma posição Y.
Suas regras de layout não devem entrar em conflito. Ou seja, você não pode especificar que uma vista deve estar a 10 pontos da borda esquerda, a 10 pontos da borda direita e a 1.000 pontos de largura. A tela do iPhone 5 tem apenas 320 pontos de largura, então seu layout é matematicamente impossível. O Auto Layout tentará se recuperar desses problemas quebrando regras até encontrar uma solução, mas o resultado final nunca é o que você deseja.
Depois declaramos um Outlet strong na nossa DetailViewController antes do método viewDidLoad
class DetailViewController: UIViewController {
@IBOutlet var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
Agora precisamos carregar nossas imagens com UIImage
O próximo objetivo é mostrar a tela de detalhes quando qualquer linha da tabela é tocada e mostrar a imagem selecionada.
Para fazer isso funcionar, precisamos adicionar outro método com nome especial ao ViewController. Este é chamado tableView(_, didSelectRowAt:), que assume um IndexPath valor como cellForRowAt esse que nos informa com qual linha estamos trabalhando. Mas antes precisamos fazer algumas coisas.
Então, adicione esta propriedade na DetailViewControlleragora, logo abaixo da linha existente @IBOutlet:
var selectedImage: String?
Dentro da função didSelectRowAt precisamos fazer um unwrapping para fazer a instancia da nossa ViewController com o identifier "Detalhes" utilizando typecasting para DetailViewController, depois atribuimos valor a nossa propriedade selectedImage, e colocamos na NavigationController
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// 1: try loading the "Detail" view controller and typecasting it to be DetailViewController
if let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as? DetailViewController {
// 2: success! Set its selectedImage property
vc.selectedImage = pictures[indexPath.row]
// 3: now push it onto the navigation controller
navigationController?.pushViewController(vc, animated: true)
}
}
na DetailViewController, após a chamada da viewDidLoad realizamos um unwrapping da propriedade opcional selectedImage e com seu sucesso, atribuimos uma Imagem com UIImage(named: )
if let imageToLoad = selectedImage {
imageView.image = UIImage(named: imageToLoad)
}
Para resolvermos o problema das imagens estarem esticadas demais, abra o Interface Builder, selecione a UIImageView. Abra o attributes inspector se você não gosta de procurá-lo, basta pressionar Cmd+Alt+4 para acessá-lo. O alongamento é causado pelo modo de visualização, que é um botão suspenso cujo padrão é “Aspect Fit” ou “Aspect Fill” dependendo da sua versão do Xcode. Tente mudar para “Aspect Fill”
Nosso proximo objetivo é permitir que os usuários visualizem as imagens em tela cheia, sem nenhuma barra de navegação atrapalhando. Existe uma maneira muito fácil de fazer isso acontecer, e é uma propriedade UINavigationControllerchamada hidesBarsOnTap.
Precisamos tomar alguns cuidados ao utilizar o hidesBarsOnTap, se o tivéssemos ativado o tempo todo, isso afetaria os toques na TableView, o que causaria estragos quando o usuário tentasse selecionar coisas. Portanto, precisamos habilitá-lo ao mostrar o ViewController de detalhes e, em seguida, desabilitá-lo ao ocultá-lo.
Por esse motivo iremos utilizar esses dois métodos, viewWillAppear() e viewWillDisappear() mas existem outros como:
Abra DetailViewController.swift e adicione estes dois novos métodos diretamente abaixo do final do viewDidLoad():
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.hidesBarsOnTap = true
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.hidesBarsOnTap = false
}
E para finalizar, vamos deixar nosso title da nossa ViewController large, e mudarmos seu valor.
title = "Storm Viewer"
navigationController?.navigationBar.prefersLargeTitles = true
Para o titulo da nossa view de detalhes faremos dinâmica, pegando o nome da imagem selecionada. Adicione isso na viewDidLoad() em DetailViewController:
title = selectedImage
E a largura do nosso titulo da DetailViewController pequena. Adicione na ViewDidLoad
navigationItem.largeTitleDisplayMode = .never