[Résolu] Move et Zoom dans UIScrollView dans un masque drawRect avec contour
Bonjour,
alors voilà mon nouveau problème cornélien.
Après être parvenu (avec les conseils du bar) à régler la première version du sujet en question (avec des UIImageViews,)
- Voir http://forum.cocoacafe.fr/topic/13571-résolu-zoom-et-masque-dans-un-uiscrollview-ou-uiview/
pour le post
- Voir http://www.magnitude-6.net/ge/c.mov
pour une vidéo explicite - On remarque qu'un contour est rajouté par dessus - Le masque et le contour sont des UIImageViews.
je suis amené à traiter le même sujet, mais plutôt que deux images (images du masque et du contour), je dois faire cela avec des drawRect (UIBezierPath, etc.). Les deux images en questions sont donc créées artificiellement. Et là , cela fonctionne bien (génial) avec l'image du masque (voir nouvelle vidéo http://www.magnitude-6.net/ge/d.mov), mais dès que je rajoute le drawRect du contour, là , je n'ai plus aucun accès à la photo que je dois déplacer ou zoomer.
Quelqu'un aurait-il une magnifique idée ?
Merci d'avance
PS : Je met le code ci-après, mais c'est grosso-modo le même que dans le post pré-cité, sauf pour les drawRect en plus.
Je sais, les noms de variables ne sont pas terribles, les valeurs numériques sont arbitraires, ou style "constantes magiques" comme dirait Samir, mais je suis juste en train de faire des tests. Donc pardonnez ce laisser-aller
Le ViewController
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
@IBOutlet weak var fondImageView: UIImageView!
var myView: UIView!
var myScrollView:UIScrollView = UIScrollView()
var myImageView = UIImageView()
var myForme: Formes!
var myContourFormes: ContourFormes!
override func viewDidLoad() {
super.viewDidLoad()
// LA VIEW ENGLOBANTE
myView = UIView(frame: CGRectMake(262, 134, 500, 500))
self.view.addSubview(myView)
// LA SCROLLVIEW QUI CONTIENT LA IMAGEVIEW
myScrollView.delegate = self
myScrollView.frame = CGRectMake(0, 0, 500, 500)
myView.addSubview(myScrollView)
// LA IMAGEVIEW DE LA PHOTO
let myImage = UIImage(named: "moi.jpg")!
myImageView.image = myImage
myImageView.contentMode = UIViewContentMode.Center
myImageView.frame = CGRectMake(0, 0, myImage.size.width, myImage.size.height)
myScrollView.contentSize = myImage.size
myImageView.userInteractionEnabled = true
myScrollView.addSubview(myImageView)
myScrollView.minimumZoomScale = 1
myScrollView.maximumZoomScale = 8
myScrollView.zoomScale = 1
// LA FORME DU MASQUE
myForme = Formes(frame: CGRectMake(50,50,400,400))
myView.addSubview(myForme)
myView.maskView = myForme
// C'EST çA QUI PERTURBE LE COMPORTEMENT // LA FORME DU CONTOUR MASQUE
myContourFormes = ContourFormes(frame: CGRectMake(50,50,400,400))
myView.addSubview(myContourFormes)
// }
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return myImageView
}
}
Le masque
import UIKit
class Formes: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clearColor()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func drawRect(myRect: CGRect) {
var bPath: UIBezierPath = UIBezierPath(rect:myRect)
let context = UIGraphicsGetCurrentContext()
CGContextSetFillColorWithColor(context, UIColor(red: 255.0/255.0, green: 0.0/255.0, blue: 0.0/255.0, alpha: 1.0).CGColor)
CGContextMoveToPoint(context, 100, 50)
CGContextAddCurveToPoint(context, 100, 250, 300, 250, 300, 50)
CGContextAddCurveToPoint(context, 300, 0, 100, 0, 100, 50)
CGContextFillPath(context)
}
}
Et le contour du masque
import UIKit
class ContourFormes: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clearColor()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func drawRect(myRect: CGRect) {
var bPath: UIBezierPath = UIBezierPath(rect:myRect)
let context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, 2.0)
CGContextSetStrokeColorWithColor(context, UIColor(red: 0.0/255.0, green: 0.0/255.0, blue: 0.0/255.0, alpha: 1.0).CGColor)
CGContextMoveToPoint(context, 100, 50)
CGContextAddCurveToPoint(context, 100, 250, 300, 250, 300, 50)
CGContextAddCurveToPoint(context, 300, 0, 100, 0, 100, 50)
CGContextStrokePath(context)
// MàŠME EN METTANT UNE TRANSPARENCE çA NE FONCTIONNE PAS CGContextSetFillColorWithColor(context, UIColor.clearColor().CGColor)
CGContextFillPath(context)
// }
}
Réponses
Une piste:
Par défaut UIImageView.userInteractionEnabled = False, et c'est peut-être pour cela que ça fonctionne.
Essaie de mettre userInteractionEnabled à False sur ContourFormes.
Pour déboguer, surcharges les méthodes -touchesBegan:withEvent:, tu verras bien qui capte les appuis ou pas.
(N'oublie pas d'appeler [super touchesBegan:withEvent:] dans la méthode surchargée).
Bonjour Céroce,
merci pour ta réponse.
Effectivement, j'ai rajouter "myContourFormes.userInteractionEnabled = false" et ça fonctionne. C'est génial, bien que ContourFormes ne soit pas un UIImageView, mais un UIView.
Sans quoi, concernant "touchesBegan" et "touchesMoved", je me suis fait plein de tutos, et je ne comprend pas bien à quoi ça sert. En fait, c'est quoi l'intérêt par rapport aux GestureRecognizers ?
Et comment je peux déceler la View qui reçoit le tap ? Car je comprend comment récupérer des coordonnées, mais pas quelle View...
Merci d'avance
C'est simplement l'ancienne "mode", avant que les GestureRecognizers ne soient introduits dans le SDK iOS.
On les utilise encore lorsqu'on veut des gestes élaborés pour lesquels il n'y a pas (encore) de GestureRecognizer. Il faut également comprendre comment ça marche pour écrire ses propres Gesture Recognizers.
Ah, ok, merci jpimbert
Il faut sans doute que je t'explique pourquoi ça fonctionne.
Par défaut, une vue capte les événements de toucher. Evidemment, mettre userIntercationEnabled à False fait qu'elle ne les capte plus, c'est donc la vue en dessous qui va les recevoir.
C'est pour cela qu'Apple a mis par défaut userIntercationEnabled = True sur UIView: a priori, une vue veut recevoir ces événements.
Mais Apple a mis par défaut userIntercationEnabled = False sur UIImageView. C'est une vue qui sert avant tout à l'affichage, on ne veut pas qu'elle intercepte les événements.
Il y a sans doute une autre manière de procéder. En effet, l'implémentation de UIView retransmets les événements de toucher à son next responder. Quelque chose comme ça:
Donc, tu pourrais changer la chaà®ne des répondeurs pour que le nextResponder de ton instance de ContourForme soit la vue d'en-dessous.
P.S.: Renomme ContourForme en ContourView. Autrement, ce n'est pas évident qu'elle hérite de UIView.
Merci Céroce pour tes précisions.
Ah, true sur UIView et false sur UIImageView, ça, c'est d'l'info. J'aurais du le découvrir dans la doc, désolé.
UIImageView qui ne sert qu'à l'affichage : c'est pour ça qu'on peut mettre true sur un UIIMageView, pour y capter un événement ?
Enfin, j'ai bien compris, je crois.
Par contre, pour la chaine des répondeurs, je ne suis pas une flèche.
J'ai essayé avec ça
Dans les deux cas, il ne se passe rien. Moi, j'aime bien la solution du...
Mais, tu me confirmes que c'est moins propre ?
D'autre part, ok pour ContourForme en ContourView, mais comment on fait si on a plusieurs views avec plusieurs views de contours, genre : ContourVoiture, ContourMoto, ContourVelo, on peut pas tous les appeler ContourView. On fait quoi ? ContourView1, 2, ... ou ContourVoitureView, ContourMotoView, etc ?
Je sais, c'est une question bête, mais j'essaie d'être le plus conforme, et j'aime bien les conventions de nommage dans les langages, et il est vrai, que l'on trouve de tout dans les tutos, c'est le soucis des non-confirmés.
Note, que ce n'est pas la vue qui va désigner son next responder, c'est plutôt son contrôleur.
Non, ce n'est pas une question bête. Il y a plusieurs manières de procéder.
La première est d'avoir une seule classe ContourView qui possède une propriété 'typeContour'. Dans ta méthode drawRect, tu écrirais:
Le problème de cette approche est que la classe ContourView va grossir et devoir être modifiée pour chaque type de contour.
La deuxième approche est de créer plein de classes: ContourVoitureView, ContourMotoView, ContourVeloView.
Sachant qu'elles fonctionnent toutes de la même manière que ContourView, tu peux faire de l'héritage: une classe parente ContourView, et des classes enfants qui dessinent dans la méthode drawRect().
La troisième approche utilise la composition: la classe ContourView va faire appel à une instance de la classe ContourDrawer pour dessiner.
- ContourView aura une propriété 'contourDrawer' de type ContourDrawer. Le contrôleur devra instancier ce ContourDrawer, et fixer la propriété.
- ContourDrawer hérite de NSObject. Il possède une seule méthode drawContour(). Son implémentation est vide (méthode abstraite).
- ContourVoitureDrawer, ContourMotoDrawer et ContourVeloDrawer héritent de ContourDrawer et surchargent la méthode drawContour().
Enfin, on peut aller plus loin que l'approche 3: plutôt que créer une classe abstraite ContourDrawer, on va créer un protocole ContourDrawer, qui contient une méthode drawContour(). Par la suite, ContourView pourra donc utiliser tout objet se conformant à ce protocole. Peu importe quelle est sa classe réelle, tant qu'il possède une méthode drawContour().
En Objective-C et Java, on va préférer cette dernière approche; en fait, on préfère toujours la composition à l'héritage: c'est plus simple et plus flexible.
Pas moins propre, donc je peux rester sur
comme ça, je laisse tomber les histoire de répondeurs qui me perturbent un peu.
Sans quoi, il faut que je creuse un peu sur tes approches (pour les classes de contours) , et bien sûr la troisième. Un peu chaud pour moi, mais j'en ai déjà un peu taté, de tout ça.
Encore merci pour tes supers explications, et à moi de jouer...