I am trying to set an interactive transition with this class :
class TransitionManager: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerInteractiveTransitioning, UIViewControllerTransitioningDelegate, UIViewControllerContextTransitioning {
weak var transitionContext: UIViewControllerContextTransitioning?
var sourceViewController: UIViewController! {
didSet {
enterPanGesture = UIScreenEdgePanGestureRecognizer()
enterPanGesture.addTarget(self, action:"panned:")
enterPanGesture.edges = UIRectEdge.Left
sourceViewController.view.addGestureRecognizer(enterPanGesture)
}
}
var togoSourceViewController: UIViewController!
let duration = 1.0
var presenting = true
var reverse = false
var originFrame = CGRectNull
var shouldBeInteractive = false
private var didStartedTransition = false
private var animated = false
private var interactive = false
private var AnimationStyle = UIModalPresentationStyle(rawValue: 1)
private var didFinishedTransition = false
private var percentTransition: CGFloat = 0.0
private var enterPanGesture: UIScreenEdgePanGestureRecognizer!
private var tovc = UIViewController()
private var pointtovc = CGPoint()
private var pointfromvc = CGPoint()
private var fromvc = UIViewController()
private var generalcontainer = UIView()
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
animated = true
let container = transitionContext.containerView()
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
if reverse {
toViewController.view.center.x -= (container?.bounds.size.width)!
container?.insertSubview(toViewController.view, aboveSubview: fromViewController.view)
} else {
toViewController.view.center.x += (container?.bounds.size.width)!
container?.addSubview(toViewController.view)
}
UIView.animateWithDuration(duration, delay: 0.0, options: UIViewAnimationOptions.TransitionNone, animations: {
if self.reverse {
toViewController.view.center.x += (container?.bounds.size.width)!
} else {
toViewController.view.center.x -= (container?.bounds.size.width)!
}
}, completion: { finished in
transitionContext.completeTransition(true)
self.animated = false
self.reverse = !self.reverse
})
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return duration
}
func startInteractiveTransition(transitionContext: UIViewControllerContextTransitioning) {
interactive = true
animated = true
let container = transitionContext.containerView()
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! //ArticleView
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! //Article OU Favoris
if reverse {
toViewController.view.frame.origin.x = -fromViewController.view.frame.maxX
container?.insertSubview(toViewController.view, aboveSubview: fromViewController.view)
}
tovc = toViewController
pointtovc = toViewController.view.bounds.origin
fromvc = fromViewController
pointfromvc = fromViewController.view.bounds.origin
generalcontainer = container!
}
func containerView() -> UIView? {
return sourceViewController?.view
}
func viewControllerForKey(key: String) -> UIViewController? {
return sourceViewController?.storyboard!.instantiateViewControllerWithIdentifier(key)
}
func viewForKey(key: String) -> UIView? {
return sourceViewController?.storyboard!.instantiateViewControllerWithIdentifier(key).view
}
func initialFrameForViewController(vc: UIViewController) -> CGRect {
return vc.view.frame
}
func finalFrameForViewController(vc: UIViewController) -> CGRect {
return vc.view.frame
}
func isAnimated() -> Bool {
return animated
}
func isInteractive() -> Bool {
return interactive
}
func presentationStyle() -> UIModalPresentationStyle {
return AnimationStyle!
}
func completeTransition(didComplete: Bool) {
interactive = false
animated = false
shouldBeInteractive = false
didFinishedTransition = didComplete
transitionContext?.finishInteractiveTransition()
transitionContext?.completeTransition(true)
}
func updateInteractiveTransition(percentComplete: CGFloat) {
if self.reverse {
print(percentComplete)
self.tovc.view.frame.origin.x = (self.fromvc.view.frame.maxX * (percentComplete)) - self.fromvc.view.frame.maxX
}
}
func finishInteractiveTransition() {
UIView.animateWithDuration(duration, delay: 0.0, options: UIViewAnimationOptions.TransitionNone, animations: {
if self.reverse {
self.tovc.view.frame.origin.x = self.fromvc.view.frame.origin.x
}
}, completion: { finished in
self.animated = false
self.reverse = !self.reverse
self.completeTransition(true)
})
}
func cancelInteractiveTransition() {
UIView.animateWithDuration(duration, delay: 0.0, options: UIViewAnimationOptions.TransitionNone, animations: {
if self.reverse {
self.tovc.view.frame.origin.x = -self.fromvc.view.frame.maxX
}
}, completion: { finished in
self.animated = false
self.completeTransition(true)
})
}
func transitionWasCancelled() -> Bool {
return didFinishedTransition
}
func targetTransform() -> CGAffineTransform {
return CGAffineTransform()
}
func completionSpeed() -> CGFloat {
return 1 - percentTransition
}
func panned(pan: UIPanGestureRecognizer) {
switch pan.state {
case .Began:
animated = true
shouldBeInteractive = true
didStartedTransition = true
didFinishedTransition = false
sourceViewController?.dismissViewControllerAnimated(true, completion: nil)
updateInteractiveTransition(0)
break
case .Changed:
percentTransition = CGFloat(pan.translationInView(sourceViewController!.view).x / sourceViewController!.view.frame.width)
if percentTransition < 0.0 {
percentTransition = 0.0
} else if percentTransition > 1.0 {
percentTransition = 1.0
}
updateInteractiveTransition(percentTransition)
break
case .Ended, .Failed, .Cancelled:
animated = false
shouldBeInteractive = false
didStartedTransition = false
didFinishedTransition = true
if percentTransition < 0.8 {
cancelInteractiveTransition()
} else {
finishInteractiveTransition()
}
break
case .Possible:
break
}
}
}
The animateTransition works perfectly and dismiss my fromViewController but during my InteractiveTransition when I call finishInteractiveTransition() and then completeTransition(true), I still have my both view :
But on Apple they said :
You must call this method after your animations have completed to notify the system that the transition animation is done. The parameter you pass must indicate whether the animations completed successfully. For interactive animations, you must call this method in addition to the finishInteractiveTransition or cancelInteractiveTransition method. The best place to call this method is in the completion block of your animations.
So, what am I doing wrong ?
I am using ios9, swift 2, Xcode 7 beta 6
I found the solution :
i should call transitionContext.completeTransition(true) in function finishInteractiveTransition() but on my previous code transitionContext was not the same that in startInteractiveTransition(transitionContext: UIViewControllerContextTransitioning)
So i add one variable :
private var Context: UIViewControllerContextTransitioning?
and use this to call :
transitionContext.completeTransition(true)
or
transitionContext.completeTransition(false)
Related
I wrote the code base on Apple's example,
somehow the app will crash if accessing the setter of the custom entity component (the reveal() method)
I tried to initialize the component to see what will happen, but it crashed at anchor.addChild(card) (ViewController line 89) instead.
But it runs without any problem on iPadOS 14 which makes it even weirder.
Does anyone know why it happens and how to fix it?
my code
// CardEntity.swift
// Card-Game
//
// Created by Ian Liu on 2020/7/21.
//
import Foundation
import RealityKit
class CardEntity: Entity, HasModel, HasCollision {
required init() {
super.init()
self.components[ModelComponent] = ModelComponent(
mesh: .generateBox(width: 0.09, height: 0.005, depth: 0.09),
materials: [SimpleMaterial(
color: .orange,
isMetallic: false)
]
)
}
var card: CardComponent {
get { components[CardComponent] ?? CardComponent() }
set { components[CardComponent] = newValue }
}
}
extension CardEntity {
func reveal() {
card.revealed = true
var transform = self.transform
transform.rotation = simd_quatf(angle: 0, axis: [1, 0, 0])
move(to: transform, relativeTo: parent, duration: 0.25, timingFunction: .easeInOut)
}
func hide() {
card.revealed = false
var transform = self.transform
transform.rotation = simd_quatf(angle: .pi, axis: [1, 0, 0])
move(to: transform, relativeTo: parent, duration: 0.25, timingFunction: .easeInOut)
}
}
// CardComponent.swift
// Card-Game
//
// Created by Ian Liu on 2020/7/21.
//
import Foundation
import RealityKit
struct CardComponent: Component, Codable {
var revealed = false
var id = -1
init() {
self.revealed = false
self.id = -1
}
}
//
// ViewController.swift
// Card-Game
//
// Created by 劉學逸 on 7/16/20.
//
import UIKit
import ARKit
import RealityKit
import Combine
class ViewController: UIViewController, ARSessionDelegate {
#IBOutlet var arView: ARView!
let cardCount = 16
var prevCard: CardEntity? = nil
override func viewDidLoad() {
super.viewDidLoad()
arView.center = self.view.center
arView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
arView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
arView.session.delegate = self
let config = ARWorldTrackingConfiguration()
if ARWorldTrackingConfiguration.supportsFrameSemantics(.personSegmentation) {
config.frameSemantics.insert(.personSegmentationWithDepth)
}
config.planeDetection = [.horizontal]
arView.session.run(config)
addCoachingOverlay()
let anchor = AnchorEntity(plane: .horizontal, minimumBounds: [0.2, 0.2])
arView.scene.addAnchor(anchor)
CardComponent.registerComponent()
var cards: [CardEntity] = []
var models: [Entity] = []
for index in 1...cardCount {
let cardModel = CardEntity()
cardModel.name = "card_\((index+1)/2)"
cardModel.card.id = (index+1)/2
cards.append(cardModel)
}
var cancellable: Cancellable? = nil
// will build for a long time if load 8 models
cancellable = ModelEntity.loadModelAsync(named: "memory_card_1")
.append(ModelEntity.loadModelAsync(named: "memory_card_2"))
.append(ModelEntity.loadModelAsync(named: "memory_card_3"))
.append(ModelEntity.loadModelAsync(named: "memory_card_4"))
.append(ModelEntity.loadModelAsync(named: "memory_card_5"))
.append(ModelEntity.loadModelAsync(named: "memory_card_6"))
.append(ModelEntity.loadModelAsync(named: "memory_card_7"))
.append(ModelEntity.loadModelAsync(named: "memory_card_8"))
.collect().sink(receiveCompletion: {error in
print("error: \(error)")
cancellable?.cancel()},
receiveValue: { entities in
for (index, entity) in entities.enumerated() {
entity.setScale(SIMD3<Float>(0.0025,0.0025,0.0025), relativeTo: anchor)
entity.position = entity.position + SIMD3<Float>(0, 0.0025, 0)
entity.name = "memory_card_\(index)"
for _ in 1...2 {
models.append(entity.clone(recursive: true))
}
}
for (index, card) in cards.enumerated() {
card.addChild(models[index])
}
cards.shuffle()
for card in cards {
var flipTransform = card.transform
flipTransform.rotation = simd_quatf(angle: .pi, axis: [1, 0, 0])
card.move(to: flipTransform, relativeTo: card.parent)
}
//attach cards to the anchor
for (index, card) in cards.enumerated() {
card.generateCollisionShapes(recursive: true)
let x = Float(index % Int(sqrt(Double(self.cardCount)))) - 1.5
let z = Float(index / Int(sqrt(Double(self.cardCount)))) - 1.5
card.position = [x * 0.1, 0, z * 0.1]
anchor.addChild(card)
}
cancellable?.cancel()
})
// Adding Occlusion Box
// Create box mesh, 0.7 meters on all sides
let boxSize: Float = 0.7
let boxMesh = MeshResource.generateBox(size: boxSize)
// Create Occlusion Material
let material = OcclusionMaterial()
// Create ModelEntity using mesh and materials
let occlusionBox = ModelEntity(mesh: boxMesh, materials: [material])
// Position box with top slightly below game board
occlusionBox.position.y = -boxSize / 2 - 0.001
// Add to anchor
anchor.addChild(occlusionBox)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
arView.center = view.center
centerCoachingView()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let configuration = arView.session.configuration else { return }
arView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
arView.session.pause()
}
#IBAction func onTap(_ sender: UITapGestureRecognizer) {
let tapLocation = sender.location(in: arView)
if let entity = arView.entity(at: tapLocation) {
var card = entity
if entity.name.hasPrefix("memory_card") {
card = entity.parent!
}
guard let cardEntity = card as? CardEntity else { return }
print("tapped card id: \(cardEntity.card.id)")
if cardEntity.card.revealed {
return
} else {
cardEntity.reveal()
}
if let lastCard = prevCard {
print("last card id: \(lastCard.card.id)")
if lastCard.card.id != cardEntity.card.id {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
cardEntity.hide()
lastCard.hide()
}
}
self.prevCard = nil
} else {
print("last card id: N\\A")
prevCard = cardEntity
}
}
}
}
extension ViewController: ARCoachingOverlayViewDelegate {
func addCoachingOverlay() {
let coachingOverlay = ARCoachingOverlayView()
coachingOverlay.autoresizingMask = [.flexibleHeight, .flexibleWidth]
coachingOverlay.center = view.center
coachingOverlay.delegate = self
coachingOverlay.session = arView.session
coachingOverlay.goal = .horizontalPlane
arView.addSubview(coachingOverlay)
NSLayoutConstraint.activate([
coachingOverlay.centerXAnchor.constraint(equalTo: view.centerXAnchor),
coachingOverlay.centerYAnchor.constraint(equalTo: view.centerYAnchor),
coachingOverlay.widthAnchor.constraint(equalTo: view.widthAnchor),
coachingOverlay.heightAnchor.constraint(equalTo: view.heightAnchor)
])
coachingOverlay.activatesAutomatically = true
}
//TODO: Fix coachingView won't center when first orientation change
func centerCoachingView() {
for subview in arView.subviews {
if let coachingView = subview as? ARCoachingOverlayView {
coachingView.center = self.view.center
}
}
}
}
I want to create a set game App. For that, I have a deck of cards from which I want the cards to (animating) move on the board.
My problem is when I do this:
private func createDeck() {
allCards.forEach{ card in
card.isFaceUp = false
card.frame = Deck.frame
GameBoard.allCardsInDeck.append(card)
}
}
the cards appearing from another position then the deck.
Doesn´t Deck.frame give me the current position of the view in its superview? my card appearing from the top but my deckView is at the bottom.
this is my code ViewController:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var informationLabel: UILabel!
#IBOutlet weak var ScoreLabel: UILabel!
#IBOutlet weak var Deck: Deck!
#IBOutlet weak var GameBoard: CardBoardView! {
didSet {
let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(swipe))
swipeGesture.direction = .down
GameBoard.addGestureRecognizer(swipeGesture)
let rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(rotation))
GameBoard.addGestureRecognizer(rotationGesture)
}
}
private var gameIsStarted = false
private var game: Game = Game() {
didSet {
updateScoreLabel()
}
}
private var selectedCards = [CardSubview] ()
private var indexOfAllCards: Int = 0
private lazy var allCards = getAllCards()
private var cardsBackgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
#objc func rotation(recognizer: UIRotationGestureRecognizer){
switch recognizer.state {
case .ended: GameBoard.mixCards()
default: break
}
}
#objc private func swipe(recognizer: UISwipeGestureRecognizer) {
switch recognizer.state {
case .ended: add3Cards()
default: break
}
}
private func getAllCards () -> [CardSubview] {
var cards = [CardSubview]()
for index in 0..<game.cards.count {
cards.append(CardSubview())
cards[index].fill = game.cards[index].strokeIdentifier
cards[index].color = game.cards[index].colorIdentifier
cards[index].form = game.cards[index].form
cards[index].occurence = game.cards[index].occurenceOfForm
cards[index].addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tab)))
cards[index].backgroundColor = cardsBackgroundColor
cards[index].indexNr = index
}
return cards
}
#objc private func tab (recognizer: UITapGestureRecognizer) {
switch recognizer.state {
case .ended:
if let card = recognizer.view as? CardSubview, card.isFaceUp == true {
card.isSelected = !card.isSelected
if card.isSelected {
selectedCards.append(card)
if selectedCards.count > 2 {
let isSet = game.isSet(arrayOfIndexes: [selectedCards[0].indexNr,selectedCards[1].indexNr, selectedCards[2].indexNr]) == true
if isSet {
game.score += 1
selectedCards.forEach { card in
GameBoard.doWhenMatched(card: card)
card.isSelected = false
selectedCards.removeAll()
}
findSet()
} else if isSet == false {
game.score -= 1
selectedCards.forEach{ card in
card.isSelected = false
}
selectedCards.removeAll()
}
}
} else if card.isSelected == false, selectedCards.contains(card) {
selectedCards.remove(at: selectedCards.index(of: card)!)
}
}
default: break
}
}
private func findSet() {
if GameBoard.allOpenCards.count > 0 {
var setInOpenCards = Bool()
var allSetsInOpenCards = Int()
for index1 in 0..<GameBoard.allOpenCards.count-2 {
for index2 in (index1+1)..<GameBoard.allOpenCards.count-1 {
for index3 in (index2+1)..<GameBoard.allOpenCards.count {
setInOpenCards = game.isSet(arrayOfIndexes: [allCards[index1].indexNr, allCards[index2].indexNr, allCards[index3].indexNr])
if (setInOpenCards == true){
allSetsInOpenCards+=1
}
}
}
}
informationLabel.text = "Sets available: \(allSetsInOpenCards)"
} else {
informationLabel.text = "Sets available: \(0)"
}
}
#IBAction private func newGame() {
game = Game()
GameBoard.removeSubviewsFromDeck()
GameBoard.allCardsInDeck.removeAll()
createDeck()
GameBoard.backgroundColor = UIColor.black
GameBoard.removeSubviews()
selectedCards.removeAll()
gameIsStarted = true
allCards = getAllCards()
indexOfAllCards = 0
GameBoard.allOpenCards.removeAll()
add12Cards()
}
private func updateScoreLabel () {
if game.score >= 0 {
ScoreLabel.backgroundColor = #colorLiteral(red: 0, green: 0.9768045545, blue: 0, alpha: 0.5)
} else if game.score < 0 {
ScoreLabel.backgroundColor = #colorLiteral(red: 1, green: 0.1491314173, blue: 0, alpha: 0.5)
}
ScoreLabel.text = "Score: \(game.score)"
}
private func add3Cards() {
if indexOfAllCards < allCards.count, gameIsStarted == true {
for _ in 0...2 {
GameBoard.allOpenCards.append(GameBoard.allCardsInDeck[indexOfAllCards])
indexOfAllCards+=1
}
}
findSet()
}
private func add12Cards() {
let numbersOfFirst12Cards = 12
if gameIsStarted == true {
for _ in 0..<numbersOfFirst12Cards {
GameBoard.allOpenCards.append(GameBoard.allCardsInDeck[indexOfAllCards])
indexOfAllCards += 1
}
}
findSet()
}
private func createDeck() {
allCards.forEach{ card in
card.isFaceUp = false
card.frame = Deck.frame
GameBoard.allCardsInDeck.append(card)
}
}
}
This is my CardBoardView where all my animations happening
import UIKit
#IBDesignable
class CardBoardView: UIView {
let durationForDisappearingCards = 2.0
let delayForDisappearingCards = 0.0
lazy var animator = UIDynamicAnimator(referenceView: self)
lazy var cardBehavior = CardBehavior(in: animator)
var allOpenCards = [CardSubview]() {
didSet { addSubview(); setNeedsLayout();}
}
var allCardsInDeck = [CardSubview] () {
didSet{ setNeedsLayout()}
}
struct Layout {
static let ratio:CGFloat = 2.0
static let insetByX:CGFloat = 2.0
static let insetByY:CGFloat = 2.0
}
private func addSubview() {
for card in allOpenCards {
addSubview(card)
}
}
public func removeSubviews() {
for card in allOpenCards {
card.removeFromSuperview()
}
}
public func removeSubviewsFromDeck() {
allCardsInDeck.forEach{ card in
card.removeFromSuperview()
}
}
public func mixCards() {
var mixCards = [CardSubview]()
for _ in 0..<allOpenCards.count {
let random = allOpenCards.count.arc4random
let card = allOpenCards[random]
mixCards.append(card)
allOpenCards.remove(at: random)
}
allOpenCards = mixCards
}
public func doWhenMatched(card: CardSubview) {
// cardBehavior.addItem(card)
UIView.animate(
withDuration: self.durationForDisappearingCards,
delay: self.delayForDisappearingCards,
animations: {
card.frame = CGRect(x: (self.superview?.frame.midX)!-card.bounds.width/2, y: (self.superview?.frame.minY)!-card.bounds.height, width: card.bounds.width , height: card.bounds.height)
}, completion: { finish in
card.removeFromSuperview()
self.allOpenCards.remove(at: self.allOpenCards.index(of: card)!)
}
)
}
// let newPosition = CGRect(x: self.frame.midX-card.bounds.width/2 , y: self.frame.minY, width: card.bounds.width, height: card.bounds.height)
//
//
//
// UIView.animate(withDuration: durationForDisappearingCards, delay: delayForDisappearingCards, options: [.allowUserInteraction], animations: {card.alpha=0.0})
override func layoutSubviews() {
super.layoutSubviews()
var grid = Grid(layout: .aspectRatio(Layout.ratio), frame: self.bounds)
grid.cellCount = allOpenCards.count
var secondsForDelay:CGFloat = 0
let secondsForFlipCard:CGFloat = 0.5
for index in allOpenCards.indices {
if self.allOpenCards[index].frame != (grid[index]?.insetBy(dx: Layout.insetByX, dy: Layout.insetByY)) ?? CGRect.zero {
UIView.animate(
withDuration: 0.5,
delay: TimeInterval(secondsForDelay) ,
animations: {self.allOpenCards[index].frame = (grid[index]?.insetBy(dx: Layout.insetByX, dy: Layout.insetByY)) ?? CGRect.zero},
completion: {
finish in
if self.allOpenCards[index].isFaceUp != true {
UIView.transition(with: self.allOpenCards[index],
duration: TimeInterval(secondsForFlipCard),
options: [.transitionFlipFromLeft],
animations: {
self.allOpenCards[index].isFaceUp = true
}
)
}
}
)
secondsForDelay+=0.02
}
}
}
}
private extension Int {
var arc4random: Int {
if self > 0 {
return Int(arc4random_uniform(UInt32(self)))
} else if self < 0 {
return -Int(arc4random_uniform(UInt32(self)))
} else {
return 0
}
}
}
private extension CGFloat {
var arc4random: CGFloat {
if self > 0 {
return CGFloat(arc4random_uniform(UInt32(self)))
} else if self < 0 {
return -CGFloat(arc4random_uniform(UInt32(self)))
} else {
return 0
}
}
}
I'm working on a app with a side menu, using two ViewController in a ContainController.
And when after close the side menu ViewController(the deinit method is called), the memory just would not be released(it's about 23 MiB), until the app entered background.
The "Leaks" tool build in Xcode 9 says there's no memory leak at all...
Anyone could tell me what's wrong?
Thanks a lot in advance!
import UIKit
import CoreData
enum MenuState {
case Collapsed
case Expanding
case Expanded
}
class ContainerViewController: UIViewController {
weak var managedObjectContext: NSManagedObjectContext!
weak var mainViewController: MasterViewController!
weak var menuViewController: MenuViewController?
var currentState = MenuState.Collapsed {
didSet {
let shouldShowShadow = currentState != .Collapsed
showShadowForMainViewController(shouldShowShadow: shouldShowShadow)
}
}
override func viewDidLoad() {
super.viewDidLoad()
mainViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "mainView") as! MasterViewController
view.addSubview(mainViewController.view)
DispatchQueue.global(qos: .userInteractive).async {
self.mainViewController.managedObjectContext = self.managedObjectContext
self.addChildViewController(self.mainViewController)
self.mainViewController.didMove(toParentViewController: self)
}
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action:#selector(self.handlePanGesture(_:)))
self.mainViewController.view.addGestureRecognizer(panGestureRecognizer)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action:#selector(self.handleTapGesture))
self.mainViewController.view.addGestureRecognizer(tapGestureRecognizer)
}
#objc func handlePanGesture(_ recognizer: UIPanGestureRecognizer) {
switch(recognizer.state) {
case .began:
let dragFromLeftToRight = (recognizer.velocity(in: view).x > 0)
if (currentState == .Collapsed && dragFromLeftToRight) {
currentState = .Expanding
addMenuViewController()
}
case .changed:
let positionX = recognizer.view!.frame.origin.x + recognizer.translation(in: view).x
recognizer.view!.frame.origin.x = positionX < 0 ? 0 : positionX
let boundX = mainViewController.view.frame.width - menuViewExpandedOffset
if positionX > 0 {
recognizer.view!.frame.origin.x = positionX > boundX ? boundX : positionX
}
recognizer.setTranslation(.zero, in: view)
case .ended:
let boundX = mainViewController.view.frame.width - menuViewExpandedOffset
let hasMovedhanHalfway = (recognizer.view!.frame.origin.x > boundX * 0.3)
animateMainView(shouldExpand: hasMovedhanHalfway)
default:
break
}
}
#objc func handleTapGesture() {
if currentState == .Expanded {
animateMainView(shouldExpand: false)
}
}
//ADD MENUVC
func addMenuViewController() {
if (menuViewController == nil) {
menuViewController = UIStoryboard(name: "Main", bundle: nil)
.instantiateViewController(withIdentifier: "menuView")
as? MenuViewController
view.insertSubview(menuViewController!.view, at: 0)
addChildViewController(menuViewController!)
menuViewController!.didMove(toParentViewController: self)
}
}
let menuViewExpandedOffset: CGFloat = 480
func animateMainView(shouldExpand: Bool) {
if (shouldExpand) {
currentState = .Expanded
animateMainViewXPosition(targetPosition: mainViewController.view.frame.width -
menuViewExpandedOffset)
}
else {
animateMainViewXPosition(targetPosition: 0) { finished in
self.currentState = .Collapsed
self.menuViewController?.view.removeFromSuperview()
self.menuViewController?.removeFromParentViewController()
self.menuViewController = nil
}
}
}
func animateMainViewXPosition(targetPosition: CGFloat,
completion: ((Bool) -> Void)! = nil) {
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1.0,
initialSpringVelocity: 0, options: .curveEaseOut, animations: {
self.mainViewController.view.frame.origin.x = targetPosition
}, completion: completion)
}
func showShadowForMainViewController(shouldShowShadow: Bool) {
if (shouldShowShadow) {
mainViewController.cameraBGimage.layer.shadowOpacity = 0.6
mainViewController.cameraBGimage.layer.shadowOffset = CGSize(width: 0, height: 1)
} else {
mainViewController.cameraBGimage.layer.shadowOpacity = 0.0
}
}
}
Provably there is nothing wrong with your app. The iOS system will manage the memory the way it thinks it is best.
If your objects are deallocated when they should be, then you are fine. If you want to get a better understanding of when your views are deallocated you can always use de Allocations tool of the Instruments and filter it by your app's name.
I found some code to show PDF file and, the code has a pencil to draw on pdf but I want to change the pencil to text. When I click on button I can write a text and move the text anywhere on view
I want to add text and change the size and move it as I want.
//
// ViewController.swift
// PDFTest
//
// Created by Prashoor Chitnis on 22/11/17.
// Copyright © 2017 InfoBeans LLC. All rights reserved.
//
import UIKit
import PDFKit
struct PAIRAnn {
var drawing: PDFAnnotation;
var button: PDFAnnotation;
var currentPage: PDFPage;
}
class ViewController: UIViewController, PDFViewDelegate, DrawEventListener {
#IBOutlet weak var closeBtn: UIBarButtonItem!
#IBOutlet weak var saveBtn: UIBarButtonItem!
#IBOutlet weak var undoBtn: UIBarButtonItem!
#IBOutlet weak var pencil: UIBarButtonItem!
var selectedAnnotation = [PAIRAnn]()
var position: CGPoint?
var currentPage : PDFPage?
var pView: PDFView?
var pdfView : PDFView {
get {
if pView == nil {
pView = PDFView(frame: self.view.frame)
pView?.delegate = self
}
return pView!
}
}
var fadeView: UIView?
var messageView : UIView {
get {
if fadeView == nil {
fadeView = UIView(frame: CGRect(x: (self.view.frame.size.width - 300)/2, y: 30, width: 300, height: 55))
var frm = (fadeView?.bounds)!
frm.origin.y = 15
frm.size.height = 25
let text = UILabel(frame: frm)
text.textAlignment = .center
text.text = "Double-Click on any spot to begin"
text.font = UIFont.systemFont(ofSize: 16)
fadeView?.addSubview(text)
fadeView?.layer.cornerRadius = 27.5
fadeView?.layer.borderWidth = 1.5
fadeView?.backgroundColor = UIColor.white.withAlphaComponent(0.8)
}
return fadeView!
}
}
var drawVw: DrawScreen?
var drawView : DrawScreen {
get {
if drawVw == nil {
drawVw = DrawScreen(frame: self.view.frame)
drawVw?.listener = self
}
return drawVw!
}
}
private var gesture: UITapGestureRecognizer?
private var gestureRecognizer: UITapGestureRecognizer {
get {
if gesture == nil {
gesture = UITapGestureRecognizer.init(target: self, action: #selector(addDrawing(sender:)))
gesture?.numberOfTapsRequired = 2
}
return gesture!
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.closeBtn.isEnabled = false
self.saveBtn.isEnabled = false
self.undoBtn.isEnabled = false
self.pencil.isEnabled = true
self.view.insertSubview(self.pdfView, at: 0)
let url = Bundle.main.url(forResource: "EN8", withExtension: "pdf")
self.pdfView.document = PDFDocument.init(url: url!)
}
#IBAction func addPencil(_ sender: UIBarButtonItem) {
self.pencil.isEnabled = false
self.view.insertSubview(self.messageView, aboveSubview: self.pdfView)
self.pdfView.addGestureRecognizer(self.gestureRecognizer)
}
func removeMessage() {
if self.messageView.superview == nil {
return
}
UIView.animate(withDuration: 0.4, animations: {
self.messageView.transform = CGAffineTransform.init(translationX: 0, y: -70)
}) { (success) in
self.messageView.removeFromSuperview()
self.messageView.transform = CGAffineTransform.identity
}
}
#objc func addDrawing(sender: UITapGestureRecognizer) {
self.removeMessage()
self.position = sender.location(in: self.view)
self.view.insertSubview(self.drawView, at: 1)
currentPage = self.pdfView.currentPage
self.pdfView.go(to: currentPage!)
self.pencil.isEnabled = false
self.undoBtn.isEnabled = false
self.saveBtn.isEnabled = false
self.closeBtn.isEnabled = true
}
func pdfViewWillClick(onLink sender: PDFView, with url: URL) {
var checkVal = url.absoluteString
checkVal = checkVal.replacingOccurrences(of: "path://", with: "")
var draw: PDFAnnotation?
for ann in self.allLinks() {
let value = "\(ann.value(forAnnotationKey: PDFAnnotationKey(rawValue: "Link"))!)"
if checkVal == value {
draw = ann
break
}
}
var currpage: PDFPage? = nil
var button: PDFAnnotation?
for (btn, page) in self.allButtons() {
let value = "\(btn.value(forAnnotationKey: PDFAnnotationKey(rawValue: "_Path"))!)"
if checkVal == value {
currpage = page
button = btn
break
}
}
if draw != nil && button != nil && currpage != nil {
let color = draw?.value(forAnnotationKey: PDFAnnotationKey(rawValue: "_Color")) as! String
switch (color) {
case "red":
draw?.color = UIColor.red
break
default:
break
}
currpage?.removeAnnotation(button!)
self.editButtonItem.isEnabled = false
self.undoBtn.isEnabled = true
self.pencil.isEnabled = false
self.selectedAnnotation.append(PAIRAnn.init(drawing: draw!, button: button!, currentPage: currpage!))
}
}
#IBAction func undoAction(_ sender: Any) {
self.removeMessage()
if self.selectedAnnotation.count > 0 {
for pairs in self.selectedAnnotation {
pairs.drawing.color = UIColor.clear
pairs.currentPage.addAnnotation(pairs.button)
}
self.selectedAnnotation = []
self.closeBtn.isEnabled = false
self.saveBtn.isEnabled = false
self.undoBtn.isEnabled = false
self.pencil.isEnabled = true
}
else {
self.drawView.undo()
}
}
#IBAction func removeDrawing(sender: Any) {
self.removeMessage()
self.pdfView.removeGestureRecognizer(self.gestureRecognizer)
self.drawView.removeFromSuperview()
self.closeBtn.isEnabled = false
self.saveBtn.isEnabled = false
self.undoBtn.isEnabled = false
self.pencil.isEnabled = true
}
func drawDidBegin() {
self.undoBtn.isEnabled = true
self.saveBtn.isEnabled = true
}
func drawingGotEmpty() {
self.undoBtn.isEnabled = false
self.saveBtn.isEnabled = false
}
#IBAction func saveDrawing(_ sender: Any) {
self.pdfView.removeGestureRecognizer(self.gestureRecognizer)
self.currentPage = self.pdfView.page(for: self.drawView.touches[0].path.first!, nearest: true)
var arrayPath = [UIBezierPath]()
for touches in self.drawView.touches {
let bPth = UIBezierPath.init()
let point = self.pdfView.convert(touches.path.first!, to: self.currentPage!)
bPth.move(to: point)
if touches.path.count > 1 {
for i in 1...touches.path.count-1 {
let tch = self.pdfView.convert(touches.path[i], to: self.currentPage!)
bPth.addLine(to: tch)
}
}
arrayPath.append(bPth)
}
let ann = PDFAnnotation.init(bounds: (self.currentPage?.bounds(for: self.pdfView.displayBox))!, forType: PDFAnnotationSubtype.ink, withProperties: nil)
ann.shouldDisplay = false
for bPth in arrayPath {
ann.add(bPth)
}
ann.color = UIColor.clear
let stamp = "PAGE_\(Date.init().timeIntervalSince1970)"
ann.setValue(stamp, forAnnotationKey: PDFAnnotationKey.init(rawValue: "Link"))
ann.setValue("red", forAnnotationKey: PDFAnnotationKey.init(rawValue: "_Color"))
self.currentPage?.addAnnotation(ann)
let linkPoint = self.pdfView.convert(self.position!, to: self.currentPage!)
let linkAnn = PDFAnnotation.init(bounds: CGRect(x: linkPoint.x - 20, y: linkPoint.y - 12, width: 40, height: 24), forType: PDFAnnotationSubtype.widget, withProperties: nil)
linkAnn.widgetFieldType = .button
linkAnn.widgetDefaultStringValue = "Open"
linkAnn.widgetControlType = .pushButtonControl
linkAnn.action = PDFActionURL(url: URL(string: "path://\(stamp)")!)
linkAnn.setValue(stamp, forAnnotationKey: PDFAnnotationKey.init(rawValue: "_Path"))
linkAnn.backgroundColor = UIColor.yellow
self.currentPage?.addAnnotation(linkAnn)
self.drawView.removeFromSuperview()
self.closeBtn.isEnabled = false
self.saveBtn.isEnabled = false
self.undoBtn.isEnabled = false
self.pencil.isEnabled = true
}
func allButtons() -> [(PDFAnnotation,PDFPage)]{
var list = [(PDFAnnotation,PDFPage)]()
let checkType = PDFAnnotationSubtype.widget.rawValue.replacingOccurrences(of: "/", with: "");
for page in self.pdfView.visiblePages() {
for ann in page.annotations {
if ann.type! == checkType && ann.widgetFieldType == .button {
list.append((ann, page))
}
}
}
return list
}
func allLinks() -> [PDFAnnotation]{
var list = [PDFAnnotation]()
let checkType = PDFAnnotationSubtype.ink.rawValue.replacingOccurrences(of: "/", with: "");
for page in self.pdfView.visiblePages() {
for ann in page.annotations {
if ann.type! == checkType {
list.append(ann)
}
}
}
return list
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I looked and cannot find an answer that works for me. I have subclassed UIControl to create a double-knob slider control. I want each knob to be available for voiceover.
To do this, I create UIAccessibilityElements and add them to an array:
func addAccessibilityElements() {
axKnobs = []
let lowKnob = UIAccessibilityElement(accessibilityContainer: self)
lowKnob.accessibilityLabel = doubleKnob ? lowValueKnobAccessibilityLabel : valueKnobAccessibilityLabel
lowKnob.accessibilityPath = UIAccessibilityConvertPathToScreenCoordinates(knobBezierPath(lowKnobPoint), self)
lowKnob.accessibilityTraits = UIAccessibilityTraitAdjustable
lowKnob.accessibilityValue = "\(lowValue)"
axKnobs.append(lowKnob)
if doubleKnob, let highKnobPoint = highKnobPoint {
let highKnob = UIAccessibilityElement(accessibilityContainer: self)
highKnob.accessibilityLabel = highValueKnobAccessibilityLabel
highKnob.accessibilityPath = UIAccessibilityConvertPathToScreenCoordinates(knobBezierPath(highKnobPoint), self)
highKnob.accessibilityTraits = UIAccessibilityTraitAdjustable
highKnob.accessibilityValue = "\(highValue)"
axKnobs.append(highKnob)
}
}
This seems to work perfect. These methods are called and the interface seems to work right:
override func accessibilityElementCount() -> Int {
return axKnobs.count
}
override func indexOfAccessibilityElement(element: AnyObject) -> Int {
let index = axKnobs.indexOf(element as! UIAccessibilityElement)!
if index == 0 {
currentKnob = .Low
} else {
currentKnob = .High
}
return index
}
override func accessibilityElementAtIndex(index: Int) -> AnyObject? {
return axKnobs[index]
}
However, my last 2 methods (accessibilityIncrement and accessibilityDecrement) in the class extension aren't being called at all.
override func accessibilityIncrement() {
if currentKnob == .None {
return
}
if currentKnob == .High {
highValue = max(highValue + 10, maximumValue)
} else {
if doubleKnob {
lowValue = max(lowValue + 10, highValue - 1)
} else {
lowValue = max(lowValue + 10, maximumValue)
}
}
updateDelegate()
redraw()
}
override func accessibilityDecrement() {
if currentKnob == .None {
return
}
if currentKnob == .High {
highValue = min(highValue - 10, lowValue + 1)
} else {
lowValue = min(lowValue - 10, minimumValue)
}
updateDelegate()
redraw()
}
Any ideas why? Full project available at https://github.com/AaronBratcher/SliderTest
UIAccessibilityElements have those 2 methods called, not the UIControl subclass.
extension DLSlider {
class KnobAccessibilityElement: UIAccessibilityElement {
var onIncrement: ((knob: UIAccessibilityElement) -> Void)?
var onDecrement: ((knob: UIAccessibilityElement) -> Void)?
override func accessibilityIncrement() {
if let callback = onIncrement {
callback(knob: self)
}
}
override func accessibilityDecrement() {
if let callback = onDecrement {
callback(knob: self)
}
}
}
func addAccessibilityElements() {
axKnobs = []
let lowKnob = KnobAccessibilityElement(accessibilityContainer: self)
lowKnob.accessibilityLabel = doubleKnob ? lowValueKnobAccessibilityLabel : valueKnobAccessibilityLabel
lowKnob.accessibilityPath = UIAccessibilityConvertPathToScreenCoordinates(knobBezierPath(lowKnobPoint), self)
lowKnob.accessibilityTraits = UIAccessibilityTraitAdjustable
lowKnob.accessibilityValue = "\(lowValue)"
lowKnob.onIncrement = { [unowned self] (knob: UIAccessibilityElement) in
self.incrementKnob(knob)
}
lowKnob.onDecrement = { [unowned self] (knob: UIAccessibilityElement) in
self.decrementKnob(knob)
}
axKnobs.append(lowKnob)
if doubleKnob, let highKnobPoint = highKnobPoint {
let highKnob = KnobAccessibilityElement(accessibilityContainer: self)
highKnob.accessibilityLabel = highValueKnobAccessibilityLabel
highKnob.accessibilityPath = UIAccessibilityConvertPathToScreenCoordinates(knobBezierPath(highKnobPoint), self)
highKnob.accessibilityTraits = UIAccessibilityTraitAdjustable
highKnob.accessibilityValue = "\(highValue)"
highKnob.onIncrement = { [unowned self] (knob: UIAccessibilityElement)in
self.incrementKnob(knob)
}
highKnob.onDecrement = { [unowned self] (knob: UIAccessibilityElement) in
self.decrementKnob(knob)
}
axKnobs.append(highKnob)
}
}
override func accessibilityElementCount() -> Int {
return axKnobs.count
}
override func indexOfAccessibilityElement(element: AnyObject) -> Int {
return axKnobs.indexOf(element as! UIAccessibilityElement)!
}
override func accessibilityElementAtIndex(index: Int) -> AnyObject? {
return axKnobs[index]
}
... // other methods here
}