How to pin NSViewController to top of NSPopover during NSViewController.transition? - objective-c

I've been trying to create a sliding transition from one child view controller to another inside an NSPopover.
My problem is that the child view controllers do not stick to the top of the NSPopover during the transition. They animate in from the bottom or top:
Expected behaviour: both child view controllers should stick to the top during the transition and should simply slide over horizontally.
This is the function I wrote to trigger the transition:
func loadViewController(_ childViewController: NSViewController, withTransition transitionOptions: NSViewController.TransitionOptions?) {
addChild(childViewController)
view.addSubview(childViewController.view)
childViewController.view.layer?.borderColor = NSColor(calibratedRed: 0, green: 255, blue: 0, alpha: 1).cgColor
childViewController.view.layer?.borderWidth = 2
childViewController.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
view.layout()
let oldViewController = currentViewController
currentViewController = childViewController
oldViewController?.view.layer?.borderColor = NSColor(calibratedRed: 255, green: 0, blue: 0, alpha: 1).cgColor
oldViewController?.view.layer?.borderWidth = 2
if let oldViewController = oldViewController {
transition(from: oldViewController, to: currentViewController!, options: transitionOptions ?? .slideLeft, completionHandler: { [weak oldViewController] in
oldViewController?.removeFromParent()
oldViewController?.view.removeFromSuperview()
})
}
NSAnimationContext.runAnimationGroup({ (context) -> Void in
context.duration = 0.5
context.allowsImplicitAnimation = true
self.parentPopover?.contentSize = NSSize(width: childViewController.preferredContentSize.width, height: childViewController.preferredContentSize.height)
})
}
Any idea what could be causing the issue? I've tried playing around with the constraints of both the child and parent view controllers as well as their frame sizes. I just can't figure out what I'm doing wrong.
I've uploaded the complete reproducible example here: https://github.com/maximilianschmitt/DebugPopoverAnimation
MasterViewController.swift
ChildViewController.swift
Thanks a lot for your help!

If you expect as on below animation
then just remove update of content size from animation block, as below
self.parentPopover?.contentSize = NSSize(width: childViewController.preferredContentSize.width, height: childViewController.preferredContentSize.height)
NSAnimationContext.runAnimationGroup({ (context) -> Void in
context.duration = 0.5
context.allowsImplicitAnimation = true
}) {
oldViewController?.removeFromParent()
oldViewController?.view.removeFromSuperview()
}
Update: keep popover content animatable (above changes are not needed)
For this case the only you need is to flip coordinate system for popover content view (which is a view of MasterViewController)
class PopoverContentView: NSView {
override var isFlipped: Bool { true }
}
class MasterViewController: NSViewController {
...
override func loadView() {
self.view = PopoverContentView()
}

Related

Custom Tabbar Button without being a UITabBarController

I am building an e-commerce app and would like to create the attached image.
I am presenting this controller Modally therefore, it will not be inside a Tabbar.
Is there an easy way I can add the Tabbar and create the button without creating a TabBar like view for each iPhone?
Or would it be best to just embed it into another UITabBarController? Seems overkill...
Many thanks.
Declare Checkout button and views for toolbar & shadow.
lazy var customView: UIView = {
let vw = UIView()
vw.backgroundColor = UIColor(red: 249/255, green: 249/255, blue: 249/255, alpha: 1.0)
vw.translatesAutoresizingMaskIntoConstraints = false
return vw
}()
lazy var shadowView: UIView = {
let vw = UIView()
vw.backgroundColor = UIColor.gray
vw.translatesAutoresizingMaskIntoConstraints = false
return vw
}()
lazy var checkoutButton: UIButton = {
let btn = UIButton(type: .system)
btn.tintColor = .white
btn.layer.cornerRadius = 5
btn.titleLabel?.font = UIFont.boldSystemFont(ofSize: 20)
btn.setTitle("Checkout", for: .normal)
btn.backgroundColor = UIColor(red: 39/255, green: 39/255, blue: 39/255, alpha: 1.0)
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
Add all in viewDidLoad.
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(customView)
[customView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
//Size of UITabBar from safeArea, so its compatible for iPhone X and others
customView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -49),
customView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
customView.heightAnchor.constraint(equalToConstant: 83)].forEach{ $0.isActive = true }
view.addSubview(shadowView)
[shadowView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
shadowView.bottomAnchor.constraint(equalTo: customView.topAnchor),
shadowView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
shadowView.heightAnchor.constraint(equalToConstant: 0.5)].forEach{ $0.isActive = true }
customView.addSubview(checkoutButton)
[checkoutButton.leadingAnchor.constraint(equalTo: customView.leadingAnchor, constant: 10),
checkoutButton.topAnchor.constraint(equalTo: customView.topAnchor, constant: 4.5),
checkoutButton.trailingAnchor.constraint(equalTo: customView.trailingAnchor, constant: -10),
checkoutButton.heightAnchor.constraint(equalToConstant: 40)].forEach{ $0.isActive = true }
}

Animate the drawing of an MKPolyline on MKMapView

I’ve got an MKMapView to animate a line by adding a line, removing it, adding a minor segment and re-adding it to the map. However, this puts a lot of overhead on the phone and doesn’t look the best. I noticed Google Maps and Uber have cleanly animated lines for showing routes that run smoothly no matter what the length or route type. Does anyone have any suggestions for a solution which is less energy-draining and looks cleaner?
Thanks, SebO.
An array of coordinates will be needed. If you have only beginning and end coordinates, get array of coordinates using below code
func getPointsOnRoute(from: CLLocation?, to: CLLocation?, on mapView: MKMapView?) -> [CLLocation]? {
let NUMBER_OF_PIXELS_TO_SKIP: Int = 120
//lower number will give a more smooth animation, but will result in more layers
var ret = [Any]()
var fromPoint: CGPoint? = nil
if let aCoordinate = from?.coordinate {
fromPoint = mapView?.convert(aCoordinate, toPointTo: mapView)
}
var toPoint: CGPoint? = nil
if let aCoordinate = to?.coordinate {
toPoint = mapView?.convert(aCoordinate, toPointTo: mapView)
}
let allPixels = getAllPoints(from: fromPoint!, to: toPoint!)
var i = 0
while i < (allPixels?.count)! {
let pointVal = allPixels![i] as? NSValue
ret.append(point(toLocation: mapView, from: (pointVal?.cgPointValue)!)!)
i += NUMBER_OF_PIXELS_TO_SKIP
}
ret.append(point(toLocation: mapView, from: toPoint!)!)
return ret as? [CLLocation] }
Having array of coordinates add rendering of the overlays in MKMapViewDelegate’s delegate method — mapView(_:rendererFor:).
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
guard let polyline = overlay as? MKPolyline else {
return MKOverlayRenderer()
}
let polylineRenderer = MKPolylineRenderer(overlay: polyline)
polylineRenderer.strokeColor = .black
polylineRenderer.lineWidth = 2
return polylineRenderer
}
mapView.addOverlay(polyline) // add it to mapview
render the polyline in small segments to create the animation effect
var drawingTimer: Timer?
// ....// Somewhere in your View Controller
func animate(route: [CLLocationCoordinate2D], duration: TimeInterval, completion: (() -> Void)?) {
guard route.count > 0 else { return }
var currentStep = 1
let totalSteps = route.count
let stepDrawDuration = duration/TimeInterval(totalSteps)
var previousSegment: MKPolyline?
drawingTimer = Timer.scheduledTimer(withTimeInterval: stepDrawDuration, repeats: true) { [weak self] timer in
guard let self = self else {
// Invalidate animation if we can't retain self
timer.invalidate()
completion?()
return
}
if let previous = previousSegment {
// Remove last drawn segment if needed.
self.mapView.removeOverlay(previous)
previousSegment = nil
}
guard currentStep < totalSteps else {
// If this is the last animation step...
let finalPolyline = MKPolyline(coordinates: route, count: route.count)
self.mapView.addOverlay(finalPolyline)
// Assign the final polyline instance to the class property.
self.polyline = finalPolyline
timer.invalidate()
completion?()
return
}
// Animation step.
// The current segment to draw consists of a coordinate array from 0 to the 'currentStep' taken from the route.
let subCoordinates = Array(route.prefix(upTo: currentStep))
let currentSegment = MKPolyline(coordinates: subCoordinates, count: subCoordinates.count)
self.mapView.addOverlay(currentSegment)
previousSegment = currentSegment
currentStep += 1
}
}

Buttons not clickable when inside a StackView inside a StackView

I have a UIView Subclass which presents three buttons arranged with two above one. I use a StackView to arrange the top two buttons, and the bottom arranged view is just a single button. The problem is that the button added as an arrangedSubview to the root StackView receives clicks, while the buttons inside of the StackView in the StackView do not.
(note: dependencies should not be affecting anything. DynamicButton is just a cocoapod UIButton subclass. InsetView is my own UIView subclass that embeds the buttons in a view with constraints. And pinTo is just a UIView extension that adds pinning constraints to the view. These things shouldn't be the bug given that the bottom button receives clicks and is under the same conditions as the top two buttons.)
class PathContentView: UIView {
let previousButton: DynamicButton = {
let r = DynamicButton(style: .caretLeft)
r.lineWidth = 3
r.strokeColor = .white
r.highlightStokeColor = .gray
return r
}()
let nextButton: DynamicButton = {
let r = DynamicButton(style: .caretRight)
r.lineWidth = 3
r.strokeColor = .white
r.highlightStokeColor = .gray
return r
}()
let deleteButton: DynamicButton = {
let r = DynamicButton(style: .close)
r.lineWidth = 3
r.strokeColor = .white
r.highlightStokeColor = .gray
return r
}()
init() {
super.init(frame: .zero)
setup()
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
private func setup() {
// button components
let deleteButtonHolder = InsetView(width: 25, height: 25, view: deleteButton)
let previousButtonHolder = InsetView(width: 25, height: 25, view: previousButton)
let nextButtonHolder = InsetView(width: 25, height: 25, view: nextButton)
let rootStackView = UIStackView()
let topSectionStackView = UIStackView()
topSectionStackView.distribution = .fillEqually
topSectionStackView.alignment = .center
topSectionStackView.spacing = 20
topSectionStackView.axis = .horizontal
rootStackView.axis = .vertical
rootStackView.distribution = .fillEqually
rootStackView.alignment = .fill
rootStackView.isLayoutMarginsRelativeArrangement = true
rootStackView.layoutMargins = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
addSubview(rootStackView)
topSectionStackView.addArrangedSubview(previousButtonHolder)
topSectionStackView.addArrangedSubview(nextButtonHolder)
rootStackView.addArrangedSubview(deleteButtonHolder)
rootStackView.addArrangedSubview(topSectionStackView)
rootStackView.pinTo(superView: self)
rootStackView.isUserInteractionEnabled = true
topSectionStackView.isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
func tapped() {
print("tapped")
}
}

func collectionViewContentSize in Swift3

I have update my Project to Swift3 in Xcode 8 and it comes this error but I have no idea what I can make there. I have already search in google but nothing founded.
Have anyone an Idea what I can make ?
Here the Error:
Method 'collectionViewContentSize()' with Objective-C selector 'collectionViewContentSize' conflicts with getter for 'collectionViewContentSize' from superclass 'UICollectionViewLayout' with the same Objective-C selector
public func collectionViewContentSize() -> CGSize {
let numberOfSections = collectionView?.numberOfSections
if numberOfSections == 0 {
return CGSize.zero
}
var contentSize = collectionView?.bounds.size
contentSize?.height = CGFloat(columnHeights[0])
return contentSize!
}
I had something similar but I was overriding collectionViewContentSize()
override func collectionViewContentSize() -> CGSize {
let collection = collectionView!
let width = collection.bounds.size.width
let height = max(posYColumn1, posYColumn2)
return CGSize(width: width, height: height)
}
I Downloaded XCode 8 beta 4 today and have had to change it to:
override var collectionViewContentSize: CGSize {
let collection = collectionView!
let width = collection.bounds.size.width
let height = max(posYColumn1, posYColumn2)
return CGSize(width: width, height: height)
}

UIView Get and Set Constraints Programatically

How can I get current constraints and set them later for a UIView. Am I doing this correctly?
Current Attempt:
Get Current Constraints
// get current constraints
var txtViewConstraints = txtView.constraints()
Set Constraints Later
override func viewDidLayoutSubviews() {
if txtViewConstraints.isEmpty == false {
txtView.addConstraints(txtViewConstraints)
}
}
Edit: This is a followup to this question: BringSubViewToFront Repositions UIView in AutoLayout
Update Code Used to Move txtView:
#IBAction func moveTxtView(sender: UIPanGestureRecognizer) {
var translation: CGPoint = sender.translationInView(self.view)
sender.view?.center = CGPointMake(sender.view!.center.x + translation.x, sender.view!.center.y + translation.y)
sender.setTranslation(CGPointMake(0, 0), inView: self.view)
txtViewConstraints = txtView.constraints()
txtViewPosition = CGRect(x: txtView.frame.origin.x, y: txtView.frame.origin.y, width: txtView.frame.size.width, height: txtView.frame.size.height)
}