How do I update a SwiftUI View in UIKit when value changes? - variables

I am trying to integrate a SwiftUI view that animates on changes to a #State variable (originally progress was #State private progress: CGFloat = 0.5 in the SwiftUI View), into an existing UIKit application. I have read through lots of search results on integrating SwiftUI into UIKit (#State, #Binding, #Environment, etc.), but can't seem to figure this out.
I create a very simple example of what I am attempting to do, as I believe once I see this answer I can adopt it to my existing application.
The Storyboard is simply View controller with a UISlider. The code below displays the SwiftUI view, but I want it to update as I move the UISlider.
import SwiftUI
class ViewController: UIViewController {
var progress: CGFloat = 0.5
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let frame = CGRect(x: 20, y: 200, width: 400, height: 400)
let childView = UIHostingController(rootView: Animate_Trim(progress: progress))
addChild(childView)
childView.view.frame = frame
view.addSubview(childView.view)
childView.didMove(toParent: self)
}
#IBAction func sliderAction(_ sender: UISlider) {
progress = CGFloat(sender.value)
print("Progress: \(progress)")
}
}
struct Animate_Trim: View {
var progress: CGFloat
var body: some View {
VStack(spacing: 20) {
Circle()
.trim(from: 0, to: progress) // Animate trim
.stroke(Color.blue,
style: StrokeStyle(lineWidth: 40,
lineCap: CGLineCap.round))
.frame(height: 300)
.rotationEffect(.degrees(-90)) // Start from top
.padding(40)
.animation(.default)
Spacer()
}.font(.title)
}
}```

If you do not want to use NotificationCenter. You could use just #Published and assign or sink.
I wrote a working example in a Playground to show the concept:
//This code runs on Xcode playground
import Combine
import SwiftUI
class ObservableSlider: ObservableObject {
#Published public var value: Double = 0.0
}
class YourViewController {
var observableSlider:ObservableSlider = ObservableSlider()
private var cancellables: Set<AnyCancellable> = []
let hostController = YourHostingController() // I put it here for the sake of the example, but you do need a reference to the Hosting Controller.
init(){ // In a real VC this code would probably be on viewDidLoad
let swiftUIView = hostController.rootView
//This is where values of SwiftUI view and UIKit get glued together
self.observableSlider.$value.assign(to: \.observableSlider.value, on: swiftUIView).store(in:&self.cancellables)
}
func updateSlider() {
observableSlider.value = 8.5
}
}
// In real app it would be something like:
//class YourHostingController<YourSwiftUIView> UIHostingController
class YourHostingController {
var rootView = YourSwiftUIView()
//In a real Hosting controller you would do something like:
// required init?(coder aDecoder: NSCoder){
// super.init(coder: aDecoder, rootView: YourSwiftUIView())
// }
}
struct YourSwiftUIView: View{
var body: some View {
EmptyView() // Your real SwiftUI body...
}
#ObservedObject var observableSlider: ObservableSlider = ObservableSlider()
func showValue(){
print(observableSlider.value)
}
init(){
print(observableSlider.value)
}
}
let yourVC = YourViewController() // Inits view and prints 0.0
yourVC.updateSlider() // Updates from UIKit to 8.5
yourVC.hostController.rootView.showValue() // Value in SwiftUI View is updated (prints 8.5)

The accepted answer actually doesn't answer the original question "update a SwiftUI View in UIKit..."?
IMHO, when you want to interact with UIKit you can use a notification to update the progress view:
extension Notification.Name {
static var progress: Notification.Name { return .init("progress") }
}
class ViewController: UIViewController {
var progress: CGFloat = 0.5 {
didSet {
let userinfo: [String: CGFloat] = ["progress": self.progress]
NotificationCenter.default.post(Notification(name: .progress,
object: nil,
userInfo: userinfo))
}
}
var slider: UISlider = UISlider()
override func viewDidLoad() {
super.viewDidLoad()
slider.addTarget(self, action: #selector(sliderAction(_:)), for: .valueChanged)
slider.frame = CGRect(x: 0, y: 500, width: 200, height: 50)
// Do any additional setup after loading the view.
let frame = CGRect(x: 20, y: 200, width: 400, height: 400)
let childView = UIHostingController(rootView: Animate_Trim())
addChild(childView)
childView.view.frame = frame
view.addSubview(childView.view)
view.addSubview(slider)
childView.didMove(toParent: self)
}
#IBAction func sliderAction(_ sender: UISlider) {
progress = CGFloat(sender.value)
print("Progress: \(progress)")
}
}
struct Animate_Trim: View {
#State var progress: CGFloat = 0
var notificationChanged = NotificationCenter.default.publisher(for: .progress)
var body: some View {
VStack(spacing: 20) {
Circle()
.trim(from: 0, to: progress) // Animate trim
.stroke(Color.blue,
style: StrokeStyle(lineWidth: 40,
lineCap: CGLineCap.round))
.frame(height: 300)
.rotationEffect(.degrees(-90)) // Start from top
.padding(40)
.animation(.default)
.onReceive(notificationChanged) { note in
self.progress = note.userInfo!["progress"]! as! CGFloat
}
Spacer()
}.font(.title)
}
}

Combine is your friend ...
create the model
update the model from UISlider, or any other part of your application
use the model to update your SwiftUI.View
I did simple example, solely with SwiftUI, but using the same scenario
import SwiftUI
class Model: ObservableObject {
#Published var progress: Double = 0.2
}
struct SliderView: View {
#EnvironmentObject var slidermodel: Model
var body: some View {
// this is not part of state of the View !!!
// the bindig is created directly to your global EnnvironmentObject
// be sure that it is available by
// creating the SwiftUI view that provides the window contents
// in your SceneDelegate.scene(....)
// let model = Model()
// let contentView = ContentView().environmentObject(model)
let binding = Binding<Double>(get: { () -> Double in
self.slidermodel.progress
}) { (value) in
self.slidermodel.progress = value
}
return Slider(value: binding, in: 0.0 ... 1.0)
}
}
struct ContentView: View {
#EnvironmentObject var model: Model
var body: some View {
VStack {
Circle()
.trim(from: 0, to: CGFloat(model.progress)) // Animate trim
.stroke(Color.blue,
style: StrokeStyle(lineWidth: 40,
lineCap: CGLineCap.round))
.frame(height: 300)
.rotationEffect(.degrees(-90)) // Start from top
.padding(40)
.animation(.default)
SliderView()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environmentObject(Model())
}
}
and all is nicely working together

Related

Creating Overlay / Polygon Mapkit Swift 5

I've tried to make an Overlay but nothing show on the map.
I want to make a 4 lines that make a shape like a square, that will show on the map (I added 4 CLLocationCoord).
What am I doing wrong?
Should I add some code?
I tried to add mapView.delegate = self but I don't know why it is doesn't work.
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController {
#IBOutlet weak var mapView: MKMapView!
let locationManager = CLLocationManager()
let regionInMeters: Double = 1000
override func viewDidLoad() {
super.viewDidLoad()
checkLocationServices()
//calling the method
addBoundry()
}
func addBoundry(){ //creation of a polygon
var points = [CLLocationCoordinate2DMake(52.284428, 20.989394),
CLLocationCoordinate2DMake(52.224534, 21.044326),
CLLocationCoordinate2DMake(52.209182, 20.948024),
CLLocationCoordinate2DMake(52.247143, 20.918842),]
let polygon = MKPolygon(coordinates: &points, count: points.count)
mapView.addOverlay(polygon)
}
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
if overlay is MKPolygon {
let polygonView = MKPolygonRenderer(overlay: overlay)
polygonView.strokeColor = .magenta
return polygonView
}
return MKOverlayRenderer()
}
func setupLocationManager(){
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
func centerViewOnUserLocation () {
if let location = locationManager.location?.coordinate {
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: regionInMeters, longitudinalMeters: regionInMeters)
mapView.setRegion(region, animated: true)
}
}
func checkLocationServices() {
if CLLocationManager.locationServicesEnabled() {
setupLocationManager()
checkLocationAuthorization()
} else {
// Show alert letting the user know they have to turn this on.
}
}
func checkLocationAuthorization() {
switch CLLocationManager.authorizationStatus() {
case .authorizedWhenInUse:
mapView.showsUserLocation = true
centerViewOnUserLocation()
locationManager.startUpdatingLocation()
break
case .denied:
// Show alert instructing them how to turn on perm
break
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
break
case .restricted:
// Show an alert letting them know what's up
break
case .authorizedAlways:
break
}
}
}
extension ViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {return}
let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
let region = MKCoordinateRegion.init(center: center, latitudinalMeters: regionInMeters, longitudinalMeters: regionInMeters)
mapView.setRegion(region, animated: true)
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
checkLocationAuthorization()
}
}
mapView.delegate = self
works when your class implements MKMapViewDelegate.
something like
class ViewController: UIViewController, MKMapViewDelegate {

Mkmap iOS11 clusters doesn't split up after max zoom, how to set it up?

First, my code is perfectly running.
I have well set up and mapView.register my annotation markers and cluster.
When I zoom out the annotations fusion as expected in my cluster views,
when I zoom in, same good result, except at a certain point. When too many annotations are too close from each other, the cluster view doesn't split up into my two annotation views anymore.
So I search a way to be able to setup this "zoom level" that will makes appear my two annotations even if there are really close from each other.
Here are my cluster views with a high zoom on the map:
Here if I zoom at the maximum:
Well, one of the cluster views split into two, but doesn't reveal the 4 annotations.
I also try to setup the displayPriority to be higher for my two annotations, than the cluster view, but the result is still the same.
Any ideas ?
You will need to keep track of the zoom level of the map, and reload your annotations when you cross a zoom level that you specify.
private let maxZoomLevel = 9
private var previousZoomLevel: Int?
private var currentZoomLevel: Int? {
willSet {
self.previousZoomLevel = self.currentZoomLevel
}
didSet {
// if we have crossed the max zoom level, request a refresh
// so that all annotations are redrawn with clustering enabled/disabled
guard let currentZoomLevel = self.currentZoomLevel else { return }
guard let previousZoomLevel = self.previousZoomLevel else { return }
var refreshRequired = false
if currentZoomLevel > self.maxZoomLevel && previousZoomLevel <= self.maxZoomLevel {
refreshRequired = true
}
if currentZoomLevel <= self.maxZoomLevel && previousZoomLevel > self.maxZoomLevel {
refreshRequired = true
}
if refreshRequired {
// remove the annotations and re-add them, eg
let annotations = self.mapView.annotations
self.mapView.removeAnnotations(annotations)
self.mapView.addAnnotations(annotations)
}
}
}
private var shouldCluster: Bool {
if let zoomLevel = self.currentZoomLevel, zoomLevel <= maxZoomLevel {
return false
}
return true
}
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
// https://stackoverflow.com/a/40616239/883413
let zoomWidth = mapView.visibleMapRect.size.width
let zoomLevel = Int(log2(zoomWidth))
self.currentZoomLevel = zoomLevel
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
// for me, annotation reuse doesn't work with clustering
let annotationView = CustomAnnotationView(annotation: annotation)
if self.shouldCluster {
annotationView.clusteringIdentifier = "custom-id"
} else {
annotationView.clusteringIdentifier = nil
}
return annotationView
}
In my case, ! EVERY TIME ! I didn't update the clusteringIdentifier
in "func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation)"
When the MKAnnotationView is reused by the mapView.dequeueReusableAnnotationView(withIdentifier: "identifier", for: annotation), the clusteringIdentifier will be nil. (reset)
That's the reason why the clusters doesn't work.
AnnotationView.swift
import MapKit
// MARK: - Define
struct AnnotationViewInfo {
static let identifier = "AnnotationView"
}
final class AnnotationView: MKAnnotationView {
// MARK: - Initializer
override init(annotation: MKAnnotation!, reuseIdentifier: String!) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
setView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setView()
}
// MARK: - Value
// MARK: Public
override var annotation: MKAnnotation? {
willSet { update(annotation: newValue) }
}
// MARK: - Function
// MARK: Private
private func setView() {
if #available(iOS 11.0, *) {
collisionMode = .rectangle
clusteringIdentifier = AnnotationViewInfo.identifier
}
canShowCallout = true
image = #imageLiteral(resourceName: "pin01").resizedImage(size: CGSize(width: #imageLiteral(resourceName: "pin01").size.width/4.0, height: #imageLiteral(resourceName: "pin01").size.height/4.0), scale: 1.0)
}
private func update(annotation: MKAnnotation?) {
if #available(iOS 11.0, *) {
clusteringIdentifier = AnnotationViewInfo.identifier
}
// TODO: Update the annotationView
}
}
MKMapViewDelegate
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if #available(iOS 11.0, *) {
switch annotation {
case is PointAnnotation: return mapView.dequeueReusableAnnotationView(withIdentifier: AnnotationView1Info.identifier, for: annotation)
case is MKClusterAnnotation: return mapView.dequeueReusableAnnotationView(withIdentifier: ClusterAnnotationViewInfo.identifier, for: annotation)
case is MKUserLocation: return nil
default: return nil
}
} else {
return nil
}
}
Key Point (You must update the "clusteringIdentifier" every time.)
private func update(annotation: MKAnnotation?) {
if #available(iOS 11.0, *) {
clusteringIdentifier = AnnotationViewInfo.identifier
}
// TODO: Update the annotationView
}
}
Sample Project Here

tvos: UITextView focus appearance like movies App

I am building a tvos app and i want the UITextView to behave similarly like in tvos Movies app. I am specially interested in the focused appearence. Please have a look ate these two pictures.
Currently i am just adding background color to the textview when it is focused but how i can achieve this focused appearance in the attached images. here is my small code
override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
super.didUpdateFocusInContext(context, withAnimationCoordinator: coordinator)
if context.previouslyFocusedView == lessonDescriptionTxt {
coordinator.addCoordinatedAnimations({ () -> Void in
self.lessonDescriptionTxt.layer.backgroundColor = UIColor.clearColor().CGColor
}, completion: nil)
}
if context.nextFocusedView == lessonDescriptionTxt {
coordinator.addCoordinatedAnimations({ () -> Void in
self.lessonDescriptionTxt.layer.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.2).CGColor
}, completion: nil)
}
}
Also if someone can suggest how i can achieve this MORE feature in the textView when there is more text. I also read this question Make UILabel focusable and tappable (tvOS) but that does not do the job for me.
The "MORE" text appears only when the description can't fit in the space that's available. You should be able to measure the amount of space needed by text by using UIKit's -[NSAttributedString boundingRectWithSize:options:context:] method.
Implementing custom FocusableTextView class worked for me:
class FocusableTextView: UITextView {
let suffixWithMore = " ... MORE"
weak var tapDelegate: FocusableTextViewDelegate?
var currentText = ""
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
isScrollEnabled = true // must be set to true for 'stringThatFitsOnScreen' function to work
isUserInteractionEnabled = true
isSelectable = true
textAlignment = .justified
self.font = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body)
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped(_:)))
tap.allowedPressTypes = [NSNumber(value: UIPress.PressType.select.rawValue)]
self.addGestureRecognizer(tap)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// use setText(_:) function instead of assigning directly to text variable
func setText(_ txt: String) {
currentText = txt
let stringThatFits = stringThatFitsOnScreen(originalString: txt) ?? txt
if txt <= stringThatFits {
self.text = txt
} else {
let newString = makeStringWithMORESuffix(from: stringThatFits)
self.text = newString
}
}
func makeStringWithMORESuffix(from txt: String) -> String {
let newNSRange = NSMakeRange(0, txt.count - suffixWithMore.count)
let stringRange = Range(newNSRange,in: txt)!
let subString = String(txt[stringRange])
return subString + suffixWithMore
}
func stringThatFitsOnScreen(originalString: String) -> String? {
// the visible rect area the text will fit into
let userWidth = self.bounds.size.width - self.textContainerInset.right - self.textContainerInset.left
let userHeight = self.bounds.size.height - self.textContainerInset.top - self.textContainerInset.bottom
let rect = CGRect(x: 0, y: 0, width: userWidth, height: userHeight)
// we need a new UITextView object to calculate the glyphRange. This is in addition to
// the UITextView that actually shows the text (probably a IBOutlet)
let tempTextView = UITextView(frame: self.bounds)
tempTextView.font = self.font
tempTextView.text = originalString
// get the layout manager and use it to layout the text
let layoutManager = tempTextView.layoutManager
layoutManager.ensureLayout(for: tempTextView.textContainer)
// get the range of text that fits in visible rect
let rangeThatFits = layoutManager.glyphRange(forBoundingRect: rect, in: tempTextView.textContainer)
// convert from NSRange to Range
guard let stringRange = Range(rangeThatFits, in: originalString) else {
return nil
}
// return the text that fits
let subString = originalString[stringRange]
return String(subString)
}
#objc func tapped(_ gesture: UITapGestureRecognizer) {
print("user selected TextView!")
tapDelegate?.userSelectedText(currentText)
}
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
if (context.nextFocusedView == self) {
backgroundColor = .white
textColor = .black
}
else {
backgroundColor = .clear
textColor = .white
}
}
}
protocol FocusableTextViewDelegate: AnyObject {
func userSelectedText(_ txt: String)
}
When user taps the text View, you can present Alert with full text likewise:
extension YourViewController: FocusableTextViewDelegate{
func userSelectedText(_ txt: String) {
let alert = UIAlertController(title: "", message: txt, preferredStyle: .alert)
let action = UIAlertAction( title: nil, style: .cancel) {_ in }
alert.addAction(action)
self.present(alert, animated: true)
}
}
Usage:
create FocusableTextView programmatically:
assign constraints programmatically to textView (or use frame)
set text to FocusableTextView with setText(_:) method
assing your UIViewController to be the tapDelegate
override func viewDidLoad() {
super.viewDidLoad()
let textView = FocusableTextView(frame: CGRect(x: 0, y: 0,
width: 500,
height: 300),
textContainer: nil)
textView.setText("Your long text..")
textView.tapDelegate = self
}

Presenting UIViewController touch events not registering

I have a custom transition event going on with my view controllers where view controller a (VC_A) will present (VC_B).
Now when doing the transition, VC_A will still exist, but VC_B will not receive any touch events. Now if I remove the view from VC_A, VC_B will get touch events. I am not sure what I am missing in my transition that tells the app to be sending all touch events to VC_B not VC_A. I have tried setting the first responder, but that did not work, if somebody could tell me what I am missing, it would be appreciated.
//
// AnimationController.swift
// MarineWars
//
// Created by Anthony Randazzo on 5/6/15.
// Copyright (c) 2015 Anthony Randazzo. All rights reserved.
//
import Foundation
import UIKit
class TransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return AnimationController(transitionType: .Presenting)
}
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return AnimationController(transitionType: .Dismissing)
}
func interactionControllerForDismissal(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
{
return nil;
}
}
private var inSuperView : UIView?;
private var toSuperView : UIView?;
class AnimationController: NSObject, UIViewControllerAnimatedTransitioning {
enum TransitionType {
case Presenting
case Dismissing
}
var animationTransitionOptions: UIViewAnimationOptions
var inView : UIView?;
var toView : UIView?;
var fromView : UIView?;
init(transitionType: TransitionType) {
switch transitionType {
case .Presenting:
animationTransitionOptions = .TransitionFlipFromLeft
case .Dismissing:
animationTransitionOptions = .TransitionFlipFromRight
}
super.init()
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
inView = transitionContext.containerView()
toView = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)?.view // if iOS 8 only, I'd use `viewForKey`, instead
fromView = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)?.view
if(animationTransitionOptions == .TransitionFlipFromLeft)
{
NSTimer.scheduledTimerWithTimeInterval(transitionDuration(transitionContext)/2, target: self, selector: Selector("fireHalfwayIn"), userInfo: nil, repeats: false);
}
else if(animationTransitionOptions == .TransitionFlipFromRight)
{
NSTimer.scheduledTimerWithTimeInterval(transitionDuration(transitionContext)/2, target: self, selector: Selector("fireHalfwayOut"), userInfo: nil, repeats: false);
}
var scene = GameScene.instance;
scene!.paused = false;
scene!.view?.paused = false;
UIView.transitionFromView(fromView!, toView: toView!, duration: transitionDuration(transitionContext), options: animationTransitionOptions | .CurveEaseInOut | .AllowAnimatedContent) { finished in
transitionContext.completeTransition(true)
NSNotificationCenter.defaultCenter().postNotificationName("DropNotifications",object:nil);
//inSuperView is our parent
if(inSuperView == nil)
{
inSuperView = self.fromView;
println(self.fromView);
for view in self.fromView!.subviews
{
for subview in view.subviews
{
if(subview.isMemberOfClass(GameSceneView))
{
self.fromView!.resignFirstResponder()
self.toView!.becomeFirstResponder();
(subview as! GameSceneView).removeScene();
return;
}
}
if(view.isMemberOfClass(GameSceneView))
{
self.fromView!.resignFirstResponder()
self.toView!.becomeFirstResponder();
(view as! GameSceneView).removeScene();
return;
}
}
}
else
{
for view in self.toView!.subviews
{
for subview in view.subviews
{
if(subview.isMemberOfClass(GameSceneView))
{
(subview as! GameSceneView).createScene();
break;
}
}
}
}
}
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
return 1.0
}
func fireHalfwayIn()
{
//println("\nFire In\n");
var scene = GameScene.instance;
//scene!.paused = false;
if(scene!.player.grid.parent != nil)
{
scene!.currentPlayer = scene!.enemy;
scene!.player.grid.removeFromParent();
scene!.addChild(scene!.enemy.grid);
var player = scene!.currentPlayer;
player!.grid.position = GameScene.fixPositionOnScreen(CGPointMake(toView!.frame.width/2 - player!.gridSize.width/2, toView!.frame.height/2 - player!.gridSize.height/2),currentPlayer: player!);
}
}
func fireHalfwayOut()
{
// println("\nFire Out\n");
var scene = GameScene.instance;
if(scene!.enemy.grid.parent != nil)
{
scene!.enemy.grid.removeFromParent();
scene!.currentPlayer = scene!.player;
scene!.addChild(scene!.player.grid);
}
}
}
OK I found the problem, I need to add the view to the content subview
if(self.presenting)
{
self.inView!.addSubview(self.toView!);
}
This of course leads to a bug when a context is completed, removing the from view from the key window.
So I needed to add this line after the context completed:
UIApplication.sharedApplication().keyWindow!.addSubview(self.toView!);
to get it to properly work

In swift is is possible to separate set, get, willSet, and willGet from the declaration of the property?

I will start by showing you some properties from a project I'm working on...
/** Properties **/
var coordinates: Coordinates
var text: NSString = "" {
didSet {
self.needsDisplay = true
}
}
var boxViewGroup: GridBoxViewGroup
var isLocked: Bool = false {
willSet {
if isSelected || !newValue {
boxViewGroup.selectedBox = nil
}
}
}
var isSelected: Bool {
get {
if boxViewGroup.selectedBox {
return boxViewGroup.selectedBox === self
}
else {return false}
}
}
var invertedFrame: NSRect {
get {
return NSRect(x: frame.origin.x,
y: superview.bounds.height - frame.origin.y,
width: bounds.size.width,
height: bounds.size.height)
}
set {
self.frame = NSRect(x: newValue.origin.x,
y: superview.bounds.height - newValue.origin.y - newValue.height,
width: newValue.width,
height: newValue.height)
}
}
That looks a little messy right. So my question is is it possible to put get, set, willGet, and willSet methods in a separate place so that my property declarations can look like this...
var coordinates: Coordinates
var boxViewGroup: GridBoxViewGroup
var isSelected: Bool
var invertedFrame: NSRect
See like this I can actually tell what properties there are.
It's possible by spiting in into 2 classes. You can override property declaration in subclass and add Property Observers
class DataA {
var coordinates: Coordinates
var boxViewGroup: GridBoxViewGroup
var isSelected: Bool
var invertedFrame: NSRect
}
class A : DataA {
override var coordinates: Coordinates {
didSet {
//add code
}
willSet(newValue) {
//add code
}
}
}
Read more about Property Overriding in apple documentation