09/08/2023
Hvordan Lukker Man en Modal på iOS? En Dybdegående Guide
At håndtere modale vinduer på iOS, især når det kommer til at lukke dem, kan være en kilde til forvirring for mange udviklere. Traditionelt set kræver det et tryk på en "luk"-knap eller en gestus som at trække ned. Denne artikel udforsker forskellige metoder til at implementere en intuitiv og brugercentreret lukningsmekanisme for modaler på iOS, med fokus på interaktiv træk-ned-funktionalitet og håndtering af baggrunds-scrolling.

Interaktiv Lukning af Modaler med Træk-Ned Gestus
En af de mest moderne og intuitive måder at lukke en modal på iOS er ved at tillade brugeren at trække den nedad. Denne tilgang efterligner den naturlige adfærd, man ser i mange native iOS-apps, og giver en følelse af direkte manipulation. Nedenfor beskrives den tekniske implementering af denne funktion ved brug af Swift og UIKit.
Grundlæggende Arkitektur
For at opnå denne funktionalitet, benytter vi os af et samarbejde mellem tre nøglekomponenter:
- View Controller: Ansvarlig for at præsentere modalen og håndtere overgangen.
- Dismiss Animator: Definerer den visuelle animation, når modalen lukkes.
- Interactor: Styrer den interaktive del af lukningen, dvs. brugerens træk-bevægelse.
Implementering i Swift
Lad os dykke ned i koden:
View Controller Konfiguration
I din præsentations-View Controller skal du initialisere en `Interactor` og tildele den til modallens `interactor`-egenskab, samt sætte `transitioningDelegate`.
import UIKit class ViewController: UIViewController { let interactor = Interactor() override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if let destinationViewController = segue.destinationViewController as? ModalViewController { destinationViewController.transitioningDelegate = self destinationViewController.interactor = interactor } } } extension ViewController: UIViewControllerTransitioningDelegate { func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return DismissAnimator() } func interactionControllerForDismissal(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { return interactor.hasStarted ? interactor: nil } } Dismiss Animator
Denne klasse implementerer `UIViewControllerAnimatedTransitioning` og styrer selve animationen. Når brugeren trækker ned, flyttes modalens view ud af skærmen.
import UIKit class DismissAnimator: NSObject { let transitionDuration = 0.6 } extension DismissAnimator: UIViewControllerAnimatedTransitioning { func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return transitionDuration } func animateTransition(transitionContext: UIViewControllerContextTransitioning) { guard let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey), let containerView = transitionContext.containerView() else { return } if transitionContext.transitionWasCancelled() { // Hvis animationen afbrydes, sæt den tilbage containerView.insertSubview(fromVC.view, atIndex: 0) } else { let screenBounds = UIScreen.mainScreen().bounds let bottomLeftCorner = CGPoint(x: 0, y: screenBounds.height) let finalFrame = CGRect(origin: bottomLeftCorner, size: screenBounds.size) UIView.animateWithDuration(transitionDuration(transitionContext), animations: { fromVC.view.frame = finalFrame }, completion: { transitionContext.completeTransition(!$0) }) } } } Interactor
En subklasse af `UIPercentDrivenInteractiveTransition`, der fungerer som en state machine for brugerens interaktion. Den holder styr på, om brugeren har startet en gestus, og om den skal fuldføres.
import UIKit class Interactor: UIPercentDrivenInteractiveTransition { var hasStarted = false var shouldFinish = false } Modal View Controller
I den præsenterede modal-View Controller skal du tilføje en `UIPanGestureRecognizer` og forbinde dens handlinger med interactor.

import UIKit class ModalViewController: UIViewController { var interactor: Interactor! // Tilknyttes fra præsentations-VC override func viewDidLoad() { super.viewDidLoad() let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:))) view.addGestureRecognizer(panGesture) } @objc func handlePanGesture(_ sender: UIPanGestureRecognizer) { guard let interactor = interactor else { return } let translation = sender.translationInView(view) let velocity = sender.velocityInView(view) // Bestem, om gestussen skal starte interaktionen if sender.state == .Began { interactor.hasStarted = true dismiss(animated: true, completion: nil) } else if sender.state == .Changed { // Beregn fremdrift og opdater animationen let percent = abs(translation.y) / view.bounds.height interactor.shouldFinish = percent > 0.3 // Tærskel for fuldførelse interactor.update(percent) } else if sender.state == .Ended || sender.state == .Cancelled { // Fuldfør eller afbryd animationen baseret på tærsklen if interactor.shouldFinish { interactor.finish() } else { interactor.cancel() } interactor.hasStarted = false } } } Forhindring af Baggrunds-Scrolling på iOS
Når en modal præsenteres, er det ofte ønskeligt at forhindre brugeren i at scrolle i baggrundens indhold. Dette skaber en klarere brugeroplevelse, hvor fokus udelukkende er på modallen. En almindelig metode til dette, især i webudvikling med Bootstrap, involverer at ændre `body`-elementets stil. Selvom den givne løsning er for web, kan princippet overføres til native iOS-udvikling ved at deaktivere scrolling på den underliggende View Controller's scroll view, mens modallen er synlig.
Metode til Web-baserede Modaler (Bootstrap)
For webudviklere, der bruger Bootstrap, kan følgende CSS og JavaScript forhindre baggrunds-scrolling:
- CSS: Når en modal åbnes, tilføjes klassen `.modal-open` til `body`. Tilføj følgende styles til denne klasse for at fryse baggrunden:
body.modal-open { bottom: 0; left: 0; position: fixed; right: 0; top: 0; } - JavaScript: For at undgå et spring til toppen af siden, når modallen åbnes, gemmes den aktuelle scroll-position og anvendes som en negativ `top`-værdi på `body`. Når modallen lukkes, nulstilles dette.
$(function() { var $window = $(window); var $body = $("body"); var $modal = $(".modal"); var scrollDistance = 0; $modal.on("show.bs.modal", function() { scrollDistance = $window.scrollTop(); $body.css("top", scrollDistance * -1); }); $modal.on("hidden.bs.modal", function() { $body.css("top", ""); $window.scrollTop(scrollDistance); }); });
Native iOS Tilgang til Scroll-kontrol
I en native iOS-app, hvis din hoved-View Controller indeholder et `UIScrollView` (eller en `UITableView`/`UICollectionView`), kan du midlertidigt deaktivere scrolling, mens modallen er aktiv. Dette kan gøres ved at sætte `scrollView.isScrollEnabled = false` i den præsenterende View Controller, når modallen vises, og sætte det tilbage til `true`, når modallen lukkes.
CSS-baseret Lukning af Modaler
For simple modaler, der ikke kræver avanceret interaktivitet, kan CSS også bruges til at håndtere lukning. Dette er især relevant for web-baserede komponenter, hvor en "X"-knap eller en "Annuller"-knap kan styre modalens synlighed.
Eksempel på CSS og HTML Struktur
Her er et grundlæggende eksempel på, hvordan en modal kan struktureres og styres med CSS:
HTML:
× Remember me Forgot password?CSS:
/* Generel styling for alle elementer */ * {box-sizing: border-box} /* Styling for body og modals */ body { font-family: Arial, Helvetica, sans-serif; } /* The Modal (background) */ .modal { display: none; /* Hidden by default */ position: fixed; /* Stay in place */ z-index: 1; /* Sit on top */ left: 0; top: 0; width: 100%; /* Full width */ height: 100%; /* Full height */ overflow: auto; /* Enable scroll if needed */ background-color: rgb(0,0,0); /* Fallback color */ background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ padding-top: 60px; } /* Modal Content/Box */ .modal-content { background-color: #fefefe; margin: 5% auto 15% auto; /* 5% from the top, 15% from the bottom and centered */ border: 1px solid #888; width: 80%; /* Could be more or less, depending on screen size */ } /* The Modal Close Button (x) */ .close { position: absolute; top: 0; right: 25px; color: #000; font-size: 35px; font-weight: bold; } .close:hover, .close:focus { color: red; cursor: pointer; } /* Add Zoom Animation */ .animate { -webkit-animation-name: zoom; -webkit-animation-duration: 0.4s; animation-name: zoom; animation-duration: 0.4s } @-webkit-keyframes zoom { from {-webkit-transform: scale(0.1)} to {-webkit-transform: scale(1.0)} } @keyframes zoom { from {transform: scale(0.1)} to {transform: scale(1.0)} } /* Container styling */ .container { padding: 16px; } /* Style Cancel and Delete Buttons */ .cancelbtn, .deletebtn { width: auto; padding: 10px 18px; background-color: #f44336; } .cancelbtn { background-color: #f1f1f1; color: black; } /* Float cancel and delete buttons and add an equal width */ .cancelbtn, .deletebtn { float: left; width: 50%; } /* Clear floats */ .clearfix::after { content: ""; clear: both; display: table; } /* Change styles for cancel button and delete button on extra small screens */ @media screen and (max-width: 300px) { .cancelbtn, .deletebtn { width: 100%; } } Opsummering og Bedste Praksis
Lukning af modaler på iOS kan gøres på flere måder, fra den enkle CSS-styrede tilgang til den avancerede interaktive træk-ned-gestus. Valget af metode afhænger af den ønskede brugeroplevelse og kompleksiteten af applikationen.
Nøglepunkter at huske:**
- Intuitivitet: Stræb efter at efterligne velkendte brugerinteraktioner, som træk-ned-lukning.
- Feedback: Giv visuel feedback til brugeren under interaktionen, f.eks. ved at vise, hvor langt de har trukket modallen.
- Scroll-kontrol: Forhindr baggrunds-scrolling, når det er relevant, for at fastholde brugerens fokus.
- Tilgængelighed: Sørg for, at der altid er en alternativ metode til at lukke modallen, f.eks. en tydelig luk-knap.
- Testning: Test grundigt på forskellige iOS-versioner og enheder for at sikre en ensartet oplevelse.
Ved at anvende disse principper kan du skabe modale vinduer, der ikke kun er funktionelle, men også bidrager positivt til den samlede brugeroplevelse på iOS-enheder.

Ofte Stillede Spørgsmål (FAQ)
Q: Hvordan lukker jeg en modal med en knap i Swift?
A: Du kan kalde `dismissViewControllerAnimated(true, completion: nil)` fra den præsenterede modal-View Controller, når der trykkes på en knap.
Q: Kan jeg tilpasse animationen, når en modal lukkes?
A: Ja, ved at implementere `UIViewControllerTransitioningDelegate` og levere en brugerdefineret `DismissAnimator`, der overholder `UIViewControllerAnimatedTransitioning`-protokollen.
Q: Hvad er den bedste måde at forhindre baggrunds-scrolling på iOS?
A: Den mest almindelige metode er at deaktivere scrolling på den underliggende `UIScrollView` (eller dens subklasser) mens modallen er synlig.
Q: Hvordan håndterer jeg afbrydelser af træk-ned-gestussen?
A: Brug `interactor.cancel()` metoden, når brugerens gestus ikke opfylder tærsklen for at lukke modallen.
Hvis du vil læse andre artikler, der ligner Luk Modaler Intuitivt på iOS, kan du besøge kategorien Mobil.
