Programação iOS

Olá, isso levanta-me duas questões, uma genérica e outra mais específica:

1 - O prepareForSegue não é apenas chamado quando há uma transição "visual" de uma view para outra? Imaginemos que eu tenho uma table view que mostra o conteúdo desse tal places. Para além dessa view tenho uma segunda view onde faço os appends. Posso fazer vários appends seguidos sem ir para a table view que quando voltar à tableview vou ver os items todos que foram adicionados ao places?

Que views pertencem a que view controllers? Está tudo no mesmo?

2 - Um outro caso concreto:

Código:
CLGeocoder().reverseGeocodeLocation(location, completionHandler: { (placemarks, error)
                in
              
                if error != nil{
                    print(error)
                }
                else{
                    (...)
              
                let annotation = MKPointAnnotation()
                annotation.coordinate = newCoordinate
                annotation.title = title
                self.map.addAnnotation(annotation)
              
                places.append(["name":title,"lat":String(newCoordinate.latitude),"lon":String(newCoordinate.longitude)])
            })

Apesar de ter passado este places através do prepareForSegue, dentro do reverseGeocodeLocation essa variável não é reconhecida. Como posso contornar isso?

Como tens declarado o places nessa classe?
 
Não, cada view tem o seu viewController.

No PlacesViewController.swift:

Código:
class PlacesViewController: UITableViewController {
 
    var places = [Dictionary<String,String>()]

(...)

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "toMap"{
            if let mapViewController = segue.destination as? MapViewController{
                mapViewController.activePlace = activePlace
                mapViewController.places = places
            }
            else{
                print("MapViewController doesn't exist")
            }
        }
    }
}

No MapViewController.swift:

Código:
class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
 
    var places = [Dictionary<String,String>()]
(...)

É neste MapViewController que está o reverseGeocodeLocation que postei antes
 
E tu dizes que a "variável não é reconhecida", queres com isso dizer que te diz que há um erro nessa linha onde o estás a usar? Ou que simplesmente o Array está vazio?
 
Há um erro. O xcode sugere que altere para self.places . Mesmo que o faça, não adiciono nada no array places talvez porque self.places não é o mesmo array que places
 
É normal teres que colocar o self., estás dentro de uma closure, sem o self não consegues aceder à variável. O que é estranho é o facto de estares a dizer que não adiciona o elemento ao array. Tenta colocar um print de self.places antes do append e outro depois, vê as diferenças.
 
Vamos lá ver se consigo explicar com clareza o que acontece :)

o array places é inicializado por código ja com 1 local. Vamos chamá-lo "Local1"

"Local1" está no index0 de places e é mostrado na 1ª linha da tableview.

Para ir para o mapa, tenho 2 opçoes.

1 - Clicando no "Local1" vou para esse local. Ao fazer append nesta situação, o primeiro print mostra apenas o "Local1" e o segundo print mostra o "Local1" e "Local2", retrocedendo para a tableview, só vejo o "local1".

2 - Clicando num botão, vou para um local aleatório. Ao fazer append nesta situação, o primeiro print mostra "" e o segundo print mostra "", "Local2", retrocedendo para a tableview, só vejo o "local1".

Deu para perceber?
 
1 - Clicando no "Local1" vou para esse local. Ao fazer append nesta situação, o primeiro print mostra apenas o "Local1" e o segundo print mostra o "Local1" e "Local2", retrocedendo para a tableview, só vejo o "local1".

Neste caso o Local2 seria o mesmo que o Local1 por teres aberto o mapa fazendo tap no Local1, certo?

2 - Clicando num botão, vou para um local aleatório. Ao fazer append nesta situação, o primeiro print mostra "" e o segundo print mostra "", "Local2", retrocedendo para a tableview, só vejo o "local1".

Deu para perceber?

Ou seja, no tal ViewController o Array tem o valor que tu queres, o array do anterior é que não. Isso é porque o Array não é um reference type mas sim um value type. Se fosse uma reference, então ao alterares um alterarias o outro. Consigo visualizar várias maneiras de fazer o que tu queres:

- Ter um delegate que dispare quando necessário e indique ao ViewController anterior que precisa de actualizar o array com dados novos
- Ter uma propriedade que seja um closure e chamá-la quando queres adicionar um elemento
- Aceder ao último View Controller da navigation stack, ver se é um PlacesViewController e adicionar o elemento
- Ter uma propriedade to tipo PlacesViewController que colocas com o valor do PlacesViewController
- Enviar uma mensagem (através do notification center) e fazer as alteraçōes necessárias

Algumas destas soluçōes são mais tightly coupled (ou mesmo melhores) que as outras.

O que eu pessoalmente aconselhava era isto:

No teu MapViewController:

Código:
protocol MapViewControllerDelegate {

    func should(addPlace place:Dictionary<String,String>)

}


class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {


    var places = [Dictionary<String,String>()]

    var delegate : MapViewControllerDelegate?

Depois adicionas o delegate method:

Código:
   func should(addPlace place:Dictionary<String,String>){

        self.delegate?.should(addPlace: place)

    }

e chama-lo onde queres adicionar a localização, onde meteste anteriormente o 2o print.

No teu outro View controller:

Primeiro adicionas o delegate:
Código:
class ... , MapViewControllerDelegate {

depois declaras (e usas) o delegate method:
Código:
   func should(addPlace place:Dictionary<String,String>){

        places.append(place)

    }

Não te esqueças de fazer set ao delegate:
Código:
...
        if segue.identifier == "toMap"{
            if let mapViewController = segue.destination as? MapViewController{
                mapViewController.activePlace = activePlace
                mapViewController.places = places
                mapViewController.delegate = self
            }
...

Já agora, muda:
Código:
(placemarks, error)
                in

para:

Código:
(placemarks, error)
               [weak self] in

Aconselho-te a ler sobre Strong/Weak references para compreenderes o que quero dizer, se for preciso pergunta que eu explico :p
 
Última edição:
Ainda não li a tua resposta completamente mas só para esclarecer:

Neste caso o Local2 seria o mesmo que o Local1 por teres aberto o mapa fazendo tap no Local1, certo?

Não. O Local 1 é apenas a localização inicial para onde o mapa vai, eu faço append a uma localização diferente. Local 1 e 2 são diferentes.

Ou seja, no tal ViewController o Array tem o valor que tu queres, o array do anterior é que não. Isso é porque o Array não é um reference type mas sim um value type. Se fosse uma reference, então ao alterares um alterarias o outro. Consigo visualizar várias maneiras de fazer o que tu queres:

Não sei se entendi bem o que querias dizer mas nenhum dos array tem o valor que eu quero. Quando estou a ver a tabela vejo sempre o Local 1 mesmo que tenha ido ao mapa adicionar mais locais. Quando estou no mapa a adicionar locais, os tais prints que sugeriste que pusesse, nesta situação, mostra-me o index 0 sem nome "" e começa a adicionar no index 1, 2, etc. Retrocedendo para a tabela, é como se não tivesse adicionado nada, mostrado o "Local1" que estava lá desde o inicio.
 
Bom dia,

Como seria de esperar funcionou :)

Há aqui várias coisas que eu não sei porquê que são assim. Gostava de perceber o que estás a fazer. Espero não estar a ser muito chato.

No teu MapViewController:

Código:
protocol MapViewControllerDelegate {

    func should(addPlace place:Dictionary<String,String>)

}


class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {


    var places = [Dictionary<String,String>()]

    var delegate : MapViewControllerDelegate?

Porquê que o delegate é opcional?

Depois adicionas o delegate method:

Código:
   func should(addPlace place:Dictionary<String,String>){

        self.delegate?.should(addPlace: place)

    }

Porquê o self aqui? O que é suposto fazer esta função???

e chama-lo onde queres adicionar a localização, onde meteste anteriormente o 2o print.

No teu outro View controller:

Primeiro adicionas o delegate:
Código:
class ... , MapViewControllerDelegate {

depois declaras (e usas) o delegate method:
Código:
   func should(addPlace place:Dictionary<String,String>){

        places.append(place)

    }

Aqui a função shouldAddPlace teve que respeitar o prototipo mas o conteúdo é diferente...na outra tinha self.delegate?.should(addPlace: place). Fiquei confuso :)

Não te esqueças de fazer set ao delegate:
Código:
...
        if segue.identifier == "toMap"{
            if let mapViewController = segue.destination as? MapViewController{
                mapViewController.activePlace = activePlace
                mapViewController.places = places
                mapViewController.delegate = self
            }
...

Dá para explicares porquê que o set ao delegate é feito dessa forma?
Ainda não alterei o weak/strong
 
Boas,

Queria aplicar um drop shadow numas labels e botões mas isto sem blur fica mal. Pelo que vi teria que fazer programáticamente com ibinspectable / ibdesignable mas não percebi muito bem como se faz isso. Será que me podem dar uma orientação?
 
Obrigado! Depois chamo a layoutSubviews() no viewDidLoad né?

e tenho que ter um bloco desses para cada elemento onde queira aplicar a shadow, correcto?
 
Tu não chamas o layoutSubviews(), esse método pertence ao lifecycle do view controller / view e é automaticamente chamado entre o viewWillAppear e o viewDidAppear (portanto depois do viewDidLoad). Não estou com isto a dizer que nunca o fiz na vida, apenas que não deve ser feito.

Repara também que aquele layoutSubviews() pertence à custom View, não ao view controller. Se quiseres ter o método disponível e a funcionar para qualquer View, eu pessoalmente iria para uma extension. Dessa forma apenas tens esse código uma vez e é aplicado a tudo. Algo como:

Código:
extension UIView {

    func addShadow(){    

       let shadowPath = UIBezierPath(rect: bounds)
       layer.masksToBounds = false
       layer.shadowColor = UIColor.black.cgColor
       layer.shadowOffset = CGSize(width: 0.0, height: 5.0)
       layer.shadowOpacity = 0.5
       layer.shadowPath = shadowPath.cgPath
       //self.setNeedsLayout()  //Indica que esta view precisa de ser actualizada no próximo ciclo de actualização
       //self.layoutIfNeeded()  //Verifica se a view precisa de ser actualizada (linha acima) e chama o layoutSubview se precisar. É assim que o layout Subviews deve ser chamado

    }

}

Agora, em qualquer view que tenhas basta chamar

Código:
aTuaView.addShadow()

E voilá, fica com sombra.

Se chamares este método na viewDidLoad()/viewWillAppear(), não precisas de des-comentar as duas linhas que comentei, apenas lá estão para mostrar como se deveria chamar o layoutSuviews das UIViews. Se chamares noutro ponto do life cycle (ou seja, do didAppear() para a frente), desconecta-as que fica a funcionar igualmente bem.
 
Muito obrigado pela ajuda. Tens sido incansável.

Esta linha cria um contorno rectangular. Também já vi que há uma opção para ovais e dá para fazer contornos a gosto.

let shadowPath = UIBezierPath(rect: bounds)

Será que não é possível fazer a shadow com o contorno das letras? Eu sei que dá para criar ponto a ponto, mas se o texto mudar deixa de bater certo.
 
Texto? Numa UILabel? Se a resposta for sim, repara que as labéus têm métodos como:

Código:
         aTuaLabel.layer.shadowColor = UIColor.white.cgColor
         aTuaLabel.layer.shadowRadius = 30.0
         aTuaLabel.layer.shadowOffset = CGSize(width: 0, height: 5)
 
Sim, texto numa UILabel. O radius ajuda a conseguir aproximar do efeito desejado mas parte sempre de uma sombra rectangular. Num programa de edição de imagem, seja ele o PS ou sketch e muito provavelmente todos os outros têm o mesmo comportamento: se aplicares shadow a texto, a shadow acompanha os contornos do texto. Aqui escreves um "O" e a sombra fica recto em vez de curvo. Não sei se me fiz entender.
 
Estranho...estou a ver o efeito ilustrado no "Shadow" e é +- isso que pretendo mas ao aplicar este método fica exatamente como estava antes...um retangulo à volta do texto.
 
Back
Topo