Schreiben einer Snapchat-Benutzeroberfläche auf Swift

Prolog


In einem meiner Projekte musste ich eine solche Schnittstelle in Snepchat erstellen. Wenn eine Karte mit Informationen von der Kamera über dem Bild verbleibt, ersetzen Sie sie reibungslos durch eine Volltonfarbe und ebenso gut in die entgegengesetzte Richtung. Ich persönlich war besonders fasziniert vom Übergang vom Kamerafenster zur Seitenkarte, und mit großer Freude habe ich Wege aufgezeigt, um dieses Problem zu lösen.


Links ist ein Beispiel für Snepchat, rechts ist ein Beispiel für eine Anwendung, die wir erstellen werden.



Wahrscheinlich besteht die erste Lösung darin, die UIScrollView anzupassen, die Ansichten irgendwie anzuordnen, die Paginierung zu verwenden, aber ehrlich gesagt ist die Schriftrolle so konzipiert, dass sie völlig andere Aufgaben löst. Das Aufnehmen zusätzlicher Animationen ist mühsam und verfügt nicht über die erforderliche Flexibilität Einstellungen. Daher ist es absolut ungerechtfertigt, es zur Lösung dieses Problems zu verwenden.


Der Bildlauf zwischen dem Kamerafenster und der seitlichen Registerkarte täuscht - dies ist überhaupt kein Bildlauf, sondern ein interaktiver Übergang zwischen den Ansichten, die zu verschiedenen Controllern gehören. Die Schaltflächen im unteren Teil sind normale Registerkarten, auf die wir zwischen den Controllern klicken.



Auf diese Weise verwendet Snatch eine eigene Version eines Navigationscontrollers wie UITabBarController mit benutzerdefinierten interaktiven Übergängen.


UIKit enthält zwei Optionen für Navigationscontroller, mit denen Sie Übergänge anpassen können: UINavigationController und UITabBarController . Beide haben navigationController(_:interactionControllerFor:) und tabBarController(_:interactionControllerFor:) in ihren Delegaten, mit denen wir unsere eigene interaktive Animation für den Übergang verwenden können.


tabBarController (_: InteractionControllerFor :)


Navigationscontroller (_: InteractionControllerFor :)


Aber ich möchte nicht durch die Implementierung von UITabBarController oder UINavigationController , zumal wir ihre interne Logik nicht steuern können. Deshalb habe ich beschlossen, meinen ähnlichen Controller zu schreiben, und jetzt möchte ich erzählen und zeigen, was daraus geworden ist.


Erklärung des Problems


Erstellen Sie Ihren eigenen Container-Controller, in dem Sie mithilfe interaktiver Animationen für Übergänge zwischen UITabBarController Controllern wechseln können. Verwenden Sie dazu den Standardmechanismus in UITabBarController und UINavigationController . Wir benötigen diesen Standardmechanismus, um vorgefertigte Übergangsanimationen vom Typ UIViewControllerAnimatedTransitioning bereits geschrieben wurden.


Projektvorbereitung


Normalerweise versuche ich, die Module in separate Frameworks zu verschieben. Dazu erstelle ich ein neues Anwendungsprojekt, füge dort ein zusätzliches Cocoa Touch Framework Ziel hinzu und verteile dann die Quellen im Projekt für die entsprechenden Ziele. Auf diese Weise erhalte ich ein separates Framework mit einer Testanwendung zum Debuggen.


Erstellen Sie eine Single View App .



Product Name wird unser Ziel sein.



Klicken Sie auf + , um das Ziel hinzuzufügen.



Wählen Sie Cocoa Touch Framework .



Wir nennen unser Framework den entsprechenden Namen, Xcode wählt automatisch das Projekt für unser Ziel aus und bietet an, die Binärdatei direkt in die Anwendung einzubinden. Wir sind uns einig.



Wir benötigen nicht das Standard- Main.storyboard und ViewController.swift , wir löschen sie.



Vergessen Sie auch nicht, den Wert von der Main Interface im Anwendungsziel auf der Registerkarte General zu entfernen.



Jetzt gehen wir zu AppDelegate.swift und AppDelegate.swift nur die application des folgenden Inhalts:


 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Launch our master view controller let master = MasterViewController() window = UIWindow() window?.rootViewController = master window?.makeKeyAndVisible() return true } 

Hier setzen wir unseren Controller an die Hauptstelle, so dass er nach dem Launcher erscheint.


Erstellen Sie jetzt MasterViewController diesen MasterViewController . Es bezieht sich auf die Anwendung. Daher ist es wichtig, beim Erstellen der Datei das richtige Ziel auszuwählen.



Wir werden MasterViewController von SnapchatNavigationController erben, den wir später im Framework implementieren werden. Vergessen Sie nicht, den import unseres Frameworks anzugeben. Ich gebe hier nicht den vollständigen Controller-Code an, die Auslassungen werden durch Ellipsen angezeigt ... Ich habe die Anwendung auf GitHub platziert , dort können Sie alle Details sehen. In diesem Controller interessiert uns nur die viewDidLoad() -Methode, die den Hintergrund-Controller mit der Kamera + einem transparenten Controller (Hauptfenster) + dem Controller mit der abgehenden Karte initialisiert.


 import MakingSnapchatNavigation class MasterViewController: SnapchatNavigationController { override func viewDidLoad() { super.viewDidLoad() //   let camera = CameraViewController() setBackground(vc: camera) //     var vcs: [UIViewController] = [] //    var stub = UIViewController() stub.view.backgroundColor = .clear vcs.append(stub) //  ,     stub = UIViewController() stub.view.backgroundColor = .clear //   let scroll = UIScrollView() stub.view.addSubview(scroll) //  ... //  ,      let content = GradientView() //  ... //    scroll.addSubview(content) vcs.append(stub) //     - setViewControllers(vcs: vcs) } } 

Was ist hier los? Wir erstellen einen Controller mit einer Kamera und setzen ihn mit der setBackground Methode von SnapchatNavigationController in den Hintergrund. Dieser Controller enthält ein gestrecktes Bild für die gesamte Ansicht der Kamera. Dann erstellen wir einen leeren transparenten Controller und fügen ihn dem Array hinzu. Er leitet einfach das Bild von der Kamera durch das Array. Wir können Steuerelemente darauf platzieren, einen weiteren transparenten Controller erstellen, einen Bildlauf hinzufügen, eine Ansicht mit Inhalten innerhalb des Bildlaufs hinzufügen und einen zweiten Controller hinzufügen Array und legen Sie dieses Array mit der speziellen setViewControllers Methode des übergeordneten SnapchatNavigationController .


Vergessen Sie nicht, eine Anforderung zur Verwendung der Kamera in Info.plist


 <key>NSCameraUsageDescription</key> <string>Need camera for background</string> 

In diesem Zusammenhang betrachten wir die Testanwendung als bereit und fahren mit dem interessantesten Teil fort - der Implementierung des Frameworks.


Übergeordnete Controller-Struktur


Erstellen Sie zunächst einen leeren SnapchatNavigationController . Es ist wichtig, das richtige Ziel dafür auszuwählen. Wenn alles richtig gemacht wurde, sollte die Anwendung erstellt werden. Dieser Status des Projekts kann als Referenz entladen werden.


 open class SnapchatNavigationController: UIViewController { override open func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } // MARK: - Public interface /// Sets view controllers. public func setViewControllers(vcs: [UIViewController]) { } /// Sets background view. public func setBackground(vc: UIViewController) { } } 

Fügen Sie nun die internen Komponenten hinzu, aus denen der Controller bestehen wird. Ich bringe nicht den gesamten Code hierher, sondern konzentriere mich nur auf wichtige Punkte.


Wir setzen die Variablen, um das Array der untergeordneten Controller zu speichern. Jetzt stellen wir die erforderliche Menge fest ein - 2 Stück. In Zukunft wird es möglich sein, die Steuerungslogik für die Verwendung mit einer beliebigen Anzahl von Steuerungen zu erweitern. Wir haben auch eine Variable festgelegt, um den angezeigten aktuellen Controller zu speichern.


 private let requiredChildrenAmount = 2 // MARK: - View controllers /// top child view controller private var topViewController: UIViewController? /// all children view controllers private var children: [UIViewController] = [] 

Erstellen Sie die Ansichten. Wir benötigen eine Ansicht für den Hintergrund, eine Ansicht mit dem Effekt, den wir beim Ändern des Controllers auf den Hintergrund anwenden möchten. Wir haben auch einen Ansichtscontainer für den aktuellen untergeordneten Controller und eine Ansichtsanzeige, die dem Benutzer sagt, wie er mit der Navigation arbeiten soll.


 // MARK: - Views private let backgroundViewContainer = UIView() private let backgroundBlurEffectView: UIVisualEffectView = { let backgroundBlurEffect = UIBlurEffect(style: UIBlurEffectStyle.light) let backgroundBlurEffectView = UIVisualEffectView(effect: backgroundBlurEffect) backgroundBlurEffectView.alpha = 0 return backgroundBlurEffectView }() /// content view for children private let contentViewContainer = UIView() private let swipeIndicatorView = UIView() 

Im nächsten Block setzen wir zwei Variablen, swipeAnimator ist für die Animation verantwortlich, swipeInteractor ist für die Interaktion verantwortlich (die Fähigkeit, den Fortschritt der Animation zu steuern), wir müssen sie während des Controller-Starts initialisieren, damit wir das Entpacken erzwingen.


 // MARK: - Animation and transition private let swipeAnimator = AnimatedTransitioning() private var swipeInteractor: CustomSwipeInteractor! 

Wir setzen auch die Transformation für den Indikator. Wir verschieben den Indikator um die Breite des Containers + doppelte Verschiebung von der Kante + die Breite des Indikators selbst, so dass sich der Indikator am gegenüberliegenden Ende des Containers befindet. Die Breite des Containers wird während der Anwendung bekannt, sodass die Variable unterwegs berechnet wird.


 // MARK: - Animation transforms private var swipeIndicatorViewTransform: CGAffineTransform { get { return CGAffineTransform(translationX: -contentViewContainer.bounds.size.width + (swipeIndicatorViewXShift * 2) + swipeIndicatorViewWidth, y: 0) } } 

Während des Ladens des Controllers weisen wir der Animation self zu (wir implementieren das entsprechende Protokoll unten) und initialisieren den Interaktor basierend auf unserer Animation, deren Fortschritt er steuert. Wir ernennen ihn auch zum Delegierten. Der Delegat reagiert auf den Beginn der Geste des Benutzers und startet je nach Status des Controllers entweder die Animation oder bricht ab. Dann fügen wir alle Ansichten zur Hauptansicht hinzu und rufen setupViews() , wodurch die Einschränkungen festgelegt werden.


 override open func viewDidLoad() { super.viewDidLoad() swipeAnimator.animation = self swipeInteractor = CustomSwipeInteractor(with: swipeAnimator) swipeInteractor.delegate = self view.addSubview(backgroundViewContainer) view.addSubview(backgroundBlurEffectView) view.addSubview(contentViewContainer) view.addSubview(swipeIndicatorView) setupViews() } 

Als nächstes fahren wir mit der Logik des Installierens und Entfernens von untergeordneten Controllern in einem Container fort. Hier ist alles so einfach wie in der Apple-Dokumentation. Wir verwenden die für diese Art der Operation vorgeschriebenen Methoden.


addChildViewController(vc) - addChildViewController(vc) dem aktuellen einen addChildViewController(vc) Controller hinzu.


contentViewContainer.addSubview(vc.view) - contentViewContainer.addSubview(vc.view) Sie die Controller-Ansicht zur Ansichtshierarchie hinzu.


vc.view.frame = contentViewContainer.bounds - vc.view.frame = contentViewContainer.bounds die Ansicht auf den gesamten Container. Da wir hier Frames anstelle des automatischen Layouts verwenden, müssen wir ihre Größe jedes Mal ändern, wenn sich die Controller-Größe ändert. Wir werden diese Logik weglassen und davon ausgehen, dass der Container die Größe der Anwendung nicht ändert, während die Anwendung ausgeführt wird.


vc.didMove(toParentViewController: self) - vc.didMove(toParentViewController: self) das Hinzufügen eines vc.didMove(toParentViewController: self) Controllers.


swipeInteractor.wireTo - Wir binden den aktuellen Controller an Benutzergesten. Später werden wir diese Methode analysieren.


 // MARK: - Private methods private func addChild(vc: UIViewController) { addChildViewController(vc) contentViewContainer.addSubview(vc.view) vc.view.frame = contentViewContainer.bounds vc.didMove(toParentViewController: self) topViewController = vc let goingRight = children.index(of: topViewController!) == 0 swipeInteractor.wireTo(viewController: topViewController!, edge: goingRight ? .right : .left) } private func removeChild(vc: UIViewController) { vc.willMove(toParentViewController: nil) vc.view.removeFromSuperview() vc.removeFromParentViewController() topViewController = nil } 

Es gibt zwei weitere Methoden, deren Code ich hier nicht setViewControllers werde: setViewControllers und setBackground . In der setViewControllers Methode setViewControllers wir einfach das Array der setViewControllers Controller in der entsprechenden Variablen unseres Controllers fest und rufen addChild auf, um einen davon in der Ansicht anzuzeigen. In der setBackground Methode machen wir dasselbe wie in addChild , nur für den Hintergrund-Controller.


Container Controller Animationslogik


Insgesamt ist die Basis unseres übergeordneten Controllers:


  • UIView ist in zwei Typen unterteilt
    • Container
    • Gewöhnlich
  • Liste der untergeordneten UIViewController
  • Ein Animationssteuerungsobjekt vom Typ swipeAnimator AnimatedTransitioning
  • Ein Objekt, das den interaktiven Verlauf einer swipeInteractor Animation vom Typ CustomSwipeInteractor
  • Interaktive Animation delegieren
  • Implementierung des Animationsprotokolls

Jetzt werden wir die letzten beiden Punkte analysieren und dann mit der Implementierung von AnimatedTransitioning und CustomSwipeInteractor .


Interaktive Animation delegieren


Der Delegat besteht nur aus einer panGestureDidStart(rightToLeftSwipe: Bool) -> Bool Methode, die den Controller über den Beginn der Geste und ihre Richtung informiert. Als Antwort wartet er auf Informationen darüber, ob die Animation als gestartet betrachtet werden kann.


Als Delegierter überprüfen wir die aktuelle Reihenfolge der Controller, um zu verstehen, ob wir die Animation in der angegebenen Richtung starten können. Wenn alles in Ordnung ist, starten wir die transition mit den folgenden Parametern: Controller, von dem aus wir uns bewegen, Controller, Bewegungsrichtung, Interaktivitätsflag (Im Falle von false wird eine zeitlich festgelegte Übergangsanimation ausgelöst).


 func panGestureDidStart(rightToLeftSwipe: Bool) -> Bool { guard let topViewController = topViewController, let fromIndex = children.index(of: topViewController) else { return false } let newIndex = rightToLeftSwipe ? 1 : 0 //   -    if newIndex > -1 && newIndex < children.count && newIndex != fromIndex { transition(from: children[fromIndex], to: children[newIndex], goingRight: rightToLeftSwipe, interactive: true) return true } return false } 

Lassen Sie uns sofort den Körper der transition untersuchen. Zunächst erstellen wir den Animationskontext für die CustomControllerContext Animation. Wir werden diese Klasse auch etwas später analysieren, da sie das UIViewControllerContextTransitioning Protokoll implementiert. Im Fall von UINavigationController und UITabBarController Instanz der Implementierung dieses Protokolls automatisch vom System erstellt und seine Logik ist uns verborgen. Wir müssen unsere eigene erstellen.


 let ctx = CustomControllerContext(fromViewController: from, toViewController: to, containerView: contentViewContainer, goingRight: goingRight) ctx.isAnimated = true ctx.isInteractive = interactive ctx.completionBlock = { (didComplete: Bool) in if didComplete { self.removeChild(vc: from) self.addChild(vc: to) } }; 

Dann rufen wir einfach entweder feste oder interaktive Animation auf. In Zukunft wird es möglich sein, eine feste an die Registerkarten der Navigation zwischen Controllern zu hängen. In diesem Beispiel werden wir dies nicht tun.


 if interactive { // Animate with interaction swipeInteractor.startInteractiveTransition(ctx) } else { // Animate without interaction swipeAnimator.animateTransition(using: ctx) } 

Animationsprotokoll


TransitionAnimation Animationsprotokoll besteht aus 4 Methoden:


addTo ist eine Methode, mit der die korrekte Struktur von addTo Ansichten im Container erstellt wird, sodass sich die vorherige Ansicht gemäß der Idee der Animation mit der neuen überlappt.


 /// Setup the views hirearchy for animation. func addTo(containerView: UIView, fromView: UIView, toView: UIView, fromLeft: Bool) 

prepare ist die Methode, die vor der Animation aufgerufen wird, um die Ansicht vorzubereiten.


 /// Setup the views position prior to the animation start. func prepare(fromView from: UIView?, toView to: UIView?, fromLeft: Bool) 

animation - die Animation selbst.


 /// The animation. func animation(fromView from: UIView?, toView to: UIView?, fromLeft: Bool) 

finalize - die erforderlichen Aktionen nach Abschluss der Animation.


 /// Cleanup the views position after the animation ended. func finalize(completed: Bool, fromView from: UIView?, toView to: UIView?, fromLeft: Bool) 

Wir werden die verwendete Implementierung nicht berücksichtigen, da ist dort alles ziemlich transparent, wir werden direkt zu den drei Hauptklassen gehen, dank derer die Animation stattfindet.


class CustomControllerContext: NSObject, UIViewControllerContextTransitioning


Der Kontext der Animation. Zur Beschreibung seiner Funktion verweisen wir auf die Hilfe des UIViewControllerContextTransitioning Protokolls:


Ein Kontextobjekt kapselt Informationen zu den am Übergang beteiligten Ansichten und Ansichtscontrollern. Es enthält auch Details zur Ausführung des Übergangs.

Das Interessanteste ist das Verbot der Anpassung dieses Protokolls:


Übernehmen Sie dieses Protokoll nicht in Ihre eigenen Klassen, und erstellen Sie keine Objekte, die dieses Protokoll übernehmen.

Aber wir brauchen es wirklich, um die Standard-Animations-Engine auszuführen, also passen wir es trotzdem an. Es hat fast keine Logik, es speichert nur den Status. Deshalb werde ich es nicht einmal hierher bringen. Sie können es auf GitHub sehen .


Es funktioniert hervorragend bei zeitlich festgelegten Animationen. Bei Verwendung für interaktive Animationen tritt jedoch ein Problem auf: UIPercentDrivenInteractiveTransition ruft eine undokumentierte Methode für den Kontext auf. Die einzig richtige Lösung in dieser Situation besteht darin, ein anderes Protokoll anzupassen - UIViewControllerInteractiveTransitioning , um Ihren eigenen Kontext zu verwenden.


class PercentDrivenInteractiveTransition: NSObject, UIViewControllerInteractiveTransitioning


Hier ist es - das Herzstück des Projekts, das das Vorhandensein interaktiver Animationen in benutzerdefinierten Container-Controllern ermöglicht. Nehmen wir es in Ordnung.


Die Klasse wird mit einem Parameter vom Typ UIViewControllerAnimatedTransitioning . Dies ist das Standardprotokoll zum Animieren des Übergangs zwischen Controllern. Auf diese Weise können wir alle Animationen verwenden, die bereits zusammen mit unserer Klasse geschrieben wurden.


 init(with animator: UIViewControllerAnimatedTransitioning) { self.animator = animator } 

Die öffentliche Schnittstelle ist recht einfach, vier Methoden, deren Funktionalität offensichtlich sein sollte.


Man muss nur den Moment beachten, in dem die Animation startet. Wir nehmen die übergeordnete Ansicht des Containers und setzen die Ebenengeschwindigkeit auf 0, damit wir den Fortschritt der Animation manuell steuern können.


 // MARK: - Public func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) { self.transitionContext = transitionContext transitionContext.containerView.superview?.layer.speed = 0 animator.animateTransition(using: transitionContext) } func updateInteractiveTransition(percentComplete: CGFloat) { setPercentComplete(percentComplete: (CGFloat(fmaxf(fminf(Float(percentComplete), 1), 0)))) } func cancelInteractiveTransition() { transitionContext?.cancelInteractiveTransition() completeTransition() } func finishInteractiveTransition() { transitionContext?.finishInteractiveTransition() completeTransition() } 

Wir wenden uns nun dem privaten Logikblock unserer Klasse zu.


setPercentComplete legt den Zeitversatz des Fortschritts der Animation für die Übersichtsebene fest und berechnet den Wert aus dem Fertigstellungsgrad und der Dauer der Animation.


 private func setPercentComplete(percentComplete: CGFloat) { setTimeOffset(timeOffset: TimeInterval(percentComplete) * duration) transitionContext?.updateInteractiveTransition(percentComplete) } private func setTimeOffset(timeOffset: TimeInterval) { transitionContext?.containerView.superview?.layer.timeOffset = timeOffset } 

completeTransition wird aufgerufen, wenn der Benutzer seine Geste gestoppt hat. Hier erstellen wir eine Instanz der CADisplayLink Klasse, mit der wir die Animation automatisch wunderschön ab dem Punkt vervollständigen können, an dem der Benutzer ihren Fortschritt nicht mehr kontrolliert. Wir fügen unseren displayLink zur run loop damit das System unseren Selektor immer dann aufruft, wenn ein neuer Frame auf dem Bildschirm des Geräts angezeigt werden muss.


 private func completeTransition() { displayLink = CADisplayLink(target: self, selector: #selector(tickAnimation)) displayLink!.add(to: .main, forMode: .commonModes) } 

In unserer Auswahl berechnen und stellen wir die vorübergehende Verschiebung des Fortschritts der Animation ein, wie wir es zuvor während der Geste des Benutzers getan haben, oder wir beenden die Animation, wenn sie ihren Start- oder Endpunkt erreicht.


 @objc private func tickAnimation() { var timeOffset = self.timeOffset() let tick = (displayLink?.duration ?? 0) * TimeInterval(completionSpeed) timeOffset += (transitionContext?.transitionWasCancelled ?? false) ? -tick : tick; if (timeOffset < 0 || timeOffset > duration) { transitionFinished() } else { setTimeOffset(timeOffset: timeOffset) } } private func timeOffset() -> TimeInterval { return transitionContext?.containerView.superview?.layer.timeOffset ?? 0 } 

Wenn Sie die Animation beendet haben, displayLink wir unseren displayLink , geben die Geschwindigkeit der Ebene zurück. Wenn die Animation nicht abgebrochen wurde, displayLink ihren endgültigen Frame erreicht hat, berechnen wir den Zeitpunkt, ab dem die Animation der Ebene beginnen soll. Weitere Informationen hierzu finden Sie im Core Animation Programming Guide oder in dieser Antwort auf den Stackoverflow.


 private func transitionFinished() { displayLink?.invalidate() guard let layer = transitionContext?.containerView.superview?.layer else { return } layer.speed = 1; let wasNotCanceled = !(transitionContext?.transitionWasCancelled ?? false) if (wasNotCanceled) { let pausedTime = layer.timeOffset layer.timeOffset = 0.0; let timeSincePause = layer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime layer.beginTime = timeSincePause } animator.animationEnded?(wasNotCanceled) } 

class AnimatedTransitioning: NSObject, UIViewControllerAnimatedTransitioning


Die letzte Klasse, die wir noch nicht untersucht haben, ist die Implementierung des UIViewControllerAnimatedTransitioning Protokolls, in dem wir die Ausführungsreihenfolge der Animationsprotokollmethoden addTo , prepare , animation , addTo . Alles hier ist ziemlich prosaisch, es ist erwähnenswert, dass nur UIViewPropertyAnimator , um Animationen anstelle der typischeren UIView.animate(withDuration:animations:) . Dies geschieht, damit der Fortschritt der Animation weiter gesteuert werden kann. Wenn sie abgebrochen wird, bringen Sie sie durch Aufrufen von finishAnimation(at: .start) an ihre ursprüngliche Position zurück, wodurch ein unnötiges Blinken des letzten Frames der Animation auf dem Bildschirm vermieden wird.


Nachwort


Wir haben eine funktionierende Demo einer Benutzeroberfläche erstellt, die der von Snapchat ähnelt. In meiner Version habe ich die Konstanten so konfiguriert, dass rechts und links von der Karte Felder vorhanden sind. Außerdem habe ich die Kamera in der Hintergrundansicht arbeiten lassen, um einen Effekt hinter der Karte zu erzeugen. Dies dient ausschließlich dazu, die Fähigkeiten dieses Ansatzes zu demonstrieren, wie er sich auf die Leistung des Geräts auswirkt, und ich habe die Batterieladung nicht überprüft.


Dieser Artikel ist mein erster Versuch, im Genre der technischen Literatur zu schreiben. Ich hätte einige wichtige Punkte übersehen können, daher beantworte ich gerne Fragen in den Kommentaren. Vielen Dank an alle, die meinen Artikel gelesen haben. Ich hoffe, Sie haben hier etwas Nützliches für sich gefunden.


Das fertige Projekt kann von GitHub unter dem Link heruntergeladen werden .


Nochmals vielen Dank, alle haben einen schönen Tag, interessante Aufgaben, produktive Codierung!



Informationsquellen


Um dieses Programm zu schreiben, habe ich die folgenden Informationen verwendet:


  1. Artikel über benutzerdefinierte Container View Controller-Übergänge von Joachim Bondo.


    Der Autor des Artikels schlug eine Variante des benutzerdefinierten Kontexts für Ziel C vor. Ich habe diese Variante verwendet, um meine Klasse in Swift zu schreiben.


    Link


  2. Interaktiver benutzerdefinierter Container View Controller Transitions-Artikel von Alek Åström


    , Objective C, Swift.


    Link


  3. SwipeableTabBarController


    , UITabBarController . .


    Link


Source: https://habr.com/ru/post/de415795/


All Articles