Sie können dies tun, indem Sie Ihre eigene Unterklasse von UIWindow
erstellen und dann eine Schaffung Instanz dieser Unterklasse.
Legen Sie seine windowLevel
auf eine sehr hohe Zahl, wie CGFloat.max
: Sie werden drei Dinge zum Fenster tun müssen. Es ist (wie von iOS 9.2) auf 10000000 geklemmt, aber Sie können es auch auf den höchstmöglichen Wert setzen.
Setzen Sie die Hintergrundfarbe des Fensters auf Null, um es transparent zu machen.
Überschreiben Sie die Methode pointInside(_:withEvent:)
des Fensters, um nur für die Punkte in der Schaltfläche true zurückzugeben. Dadurch werden nur Berührungen akzeptiert, die die Schaltfläche berühren, und alle anderen Berührungen werden an andere Fenster weitergegeben.
dann eine Wurzel-View-Controller für das Fenster erstellen. Erstellen Sie die Schaltfläche und fügen Sie sie zur Ansichtshierarchie hinzu, und teilen Sie das Fenster darüber mit, damit das Fenster sie in pointInside(_:withEvent:)
verwenden kann.
Es gibt eine letzte Sache zu tun. Es stellt sich heraus, dass die Bildschirmtastatur auch die höchste Fensterebene verwendet, und da sie nach dem Fenster auf dem Bildschirm erscheint, wird sie über Ihrem Fenster angezeigt. Sie können das beheben, indem Sie UIKeyboardDidShowNotification
beobachten und die windowLevel
Ihres Fensters zurücksetzen, wenn dies geschieht (weil dies dokumentiert ist, um Ihr Fenster über alle Fenster auf derselben Ebene zu setzen).
Hier ist eine Demo.Ich beginne mit dem View-Controller, den ich im Fenster verwenden werde.
import UIKit
class FloatingButtonController: UIViewController {
Hier ist die Schaltfläche Instanzvariable. Wer eine FloatingButtonController
erstellt, kann auf die Schaltfläche zugreifen, um ein Ziel/eine Aktion hinzuzufügen. Ich werde das später demonstrieren.
private(set) var button: UIButton!
Sie müssen diesen Initialisierer "implementieren", aber ich werde diese Klasse nicht in einem Storyboard verwenden.
required init?(coder aDecoder: NSCoder) {
fatalError()
}
Hier ist der echte Initialisierer.
init() {
super.init(nibName: nil, bundle: nil)
window.windowLevel = CGFloat.max
window.hidden = false
window.rootViewController = self
window.hidden = false
Einstellung wird es auf dem Bildschirm (wenn der Strom CATransaction
Commits). Ich muss auch sehen für die Tastatur erscheinen:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardDidShow:", name: UIKeyboardDidShowNotification, object: nil)
}
Ich brauche einen Verweis auf meinem Fenster, das eine Instanz einer benutzerdefinierten Klasse sein wird:
private let window = FloatingButtonWindow()
Ich werde meine Ansicht Hierarchie erstellen In Code, um diese Antwort in sich geschlossen zu halten:
override func loadView() {
let view = UIView()
let button = UIButton(type: .Custom)
button.setTitle("Floating", forState: .Normal)
button.setTitleColor(UIColor.greenColor(), forState: .Normal)
button.backgroundColor = UIColor.whiteColor()
button.layer.shadowColor = UIColor.blackColor().CGColor
button.layer.shadowRadius = 3
button.layer.shadowOpacity = 0.8
button.layer.shadowOffset = CGSize.zero
button.sizeToFit()
button.frame = CGRect(origin: CGPointMake(10, 10), size: button.bounds.size)
button.autoresizingMask = []
view.addSubview(button)
self.view = view
self.button = button
window.button = button
Nichts Besonderes dort los. Ich erstelle gerade meine Root-Ansicht und lege eine Schaltfläche darin ab.
der Benutzer zu ermöglichen, den Knopf um zu ziehen, werde ich einen pan Gestenerkenner der Schaltfläche hinzu:
let panner = UIPanGestureRecognizer(target: self, action: "panDidFire:")
button.addGestureRecognizer(panner)
}
Das Fenster wird seine Subviews legen, wenn es zuerst erscheint, und wenn es geändert wird (insbesondere wegen der Schnittstelle Rotation), so möchte ich den Knopf zu diesen Zeiten neu zu positionieren:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
snapButtonToSocket()
}
(Mehr auf snapButtonToSocket
kurz.)
um han DLE den Knopf ziehen, verwende ich die Pfanne Gestenerkenner auf die übliche Weise:
func panDidFire(panner: UIPanGestureRecognizer) {
let offset = panner.translationInView(view)
panner.setTranslation(CGPoint.zero, inView: view)
var center = button.center
center.x += offset.x
center.y += offset.y
button.center = center
Sie fragte nach „schnappen“, so dass, wenn die Pfanne oder abgebrochen wird am Ende, ich die Taste, um eine von einem festen Snap werden Anzahl der Positionen, die ich „Steckdosen“ nennen:
if panner.state == .Ended || panner.state == .Cancelled {
UIView.animateWithDuration(0.3) {
self.snapButtonToSocket()
}
}
}
ich behandle die Tastatur Benachrichtigung durch window.windowLevel
Zurücksetzen:
func keyboardDidShow(note: NSNotification) {
window.windowLevel = 0
window.windowLevel = CGFloat.max
}
um Snap auf die Schaltfläche an eine Steckdose, finde ich das nächste Steckdose an der Position des Knopfes und bewegen Sie den Knopf dort. Beachten Sie, dass dies nicht unbedingt das ist, was Sie bei einer Schnittstellendrehung wünschen, aber ich werde eine bessere Lösung als Übung für den Leser hinterlassen. Jedenfalls hält es nach einer Drehung den Knopf auf dem Bildschirm.
private func snapButtonToSocket() {
var bestSocket = CGPoint.zero
var distanceToBestSocket = CGFloat.infinity
let center = button.center
for socket in sockets {
let distance = hypot(center.x - socket.x, center.y - socket.y)
if distance < distanceToBestSocket {
distanceToBestSocket = distance
bestSocket = socket
}
}
button.center = bestSocket
}
habe ich eine Steckdose in jeder Ecke des Bildschirms, und ein in der Mitte zu Demonstrationszwecken:
private var sockets: [CGPoint] {
let buttonSize = button.bounds.size
let rect = view.bounds.insetBy(dx: 4 + buttonSize.width/2, dy: 4 + buttonSize.height/2)
let sockets: [CGPoint] = [
CGPointMake(rect.minX, rect.minY),
CGPointMake(rect.minX, rect.maxY),
CGPointMake(rect.maxX, rect.minY),
CGPointMake(rect.maxX, rect.maxY),
CGPointMake(rect.midX, rect.midY)
]
return sockets
}
}
Schließlich ist die benutzerdefinierte UIWindow
Unterklasse:
private class FloatingButtonWindow: UIWindow {
var button: UIButton?
init() {
super.init(frame: UIScreen.mainScreen().bounds)
backgroundColor = nil
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Wie ich erwähnt, muss ich pointInside(_:withEvent:)
überschreiben, so dass das Fenster Berührungen außerhalb der Schaltfläche ignoriert:
private override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
guard let button = button else { return false }
let buttonPoint = convertPoint(point, toView: button)
return button.pointInside(buttonPoint, withEvent: event)
}
}
Nun, wie benutzt du dieses Ding? Ich habe Apple's AdaptivePhotos sample project heruntergeladen und meine FloatingButtonController.swift
Datei zum AdaptiveCode
Ziel hinzugefügt. Ich habe eine Immobilie zu AppDelegate
:
var floatingButtonController: FloatingButtonController?
Dann habe ich Code bis zum Ende des application(_:didFinishLaunchingWithOptions:)
erstellen FloatingButtonController
:
floatingButtonController = FloatingButtonController()
floatingButtonController?.button.addTarget(self, action: "floatingButtonWasTapped", forControlEvents: .TouchUpInside)
Diese Linien fahren direkt vor dem return true
am Ende der Funktion. Ich muss auch die Aktionsmethode für die Schaltfläche schreiben:
func floatingButtonWasTapped() {
let alert = UIAlertController(title: "Warning", message: "Don't do that!", preferredStyle: .Alert)
let action = UIAlertAction(title: "Sorry…", style: .Default, handler: nil)
alert.addAction(action)
window?.rootViewController?.presentViewController(alert, animated: true, completion: nil)
}
Das war alles, was ich tun musste. Aber ich machte noch eine Sache zu Demonstrationszwecken: In AboutViewController habe ich label
in eine UITextView
geändert, damit ich eine Möglichkeit habe, die Tastatur aufzurufen.
Hier ist, was die Schaltfläche wie folgt aussieht:
Hier ist der Effekt auf die Schaltfläche des Klopfens. Beachten Sie, dass die Taste über dem Alarm schwimmt:
Hier ist, was passiert, wenn ich die Tastatur bringen:
es Drehgriff Hat? Sie wetten:
Nun, die rotations Handhabung ist nicht perfekt, weil der Sockel am nächsten ist die Taste nach einer Drehung nicht die gleichen „logischen“ socket wie vor der Drehung sein kann. Sie können dies beheben, indem Sie verfolgen, an welchem Sockel die Schaltfläche zuletzt geklickt wurde, und eine Rotation speziell behandeln (indem Sie die Größenänderung erkennen).
Ich habe die gesamte FloatingViewController.swift
in this gist für Ihre Bequemlichkeit.
Fragen Sie nach der Schaltfläche zu erhöhen? –
Wessen Modale? Woher kommen diese Modale? – Tommy
Die Modale könnten von jedem ViewController kommen, der sie anruft. – TIMEX