How do i add dynamic behaviour in NSObject class : Swift - ios

I'm trying to create a custom alertview with dynamic behaviour using UIDynamicAnimator in NSObject class using swift,While adding UISnapBehaviour to a view in NSObject class init method snap behaviour is not working,For instance look at the below code
import UIKit
class DynamicBehaviour: NSObject {
var Animator:UIDynamicAnimator!
var TargetView:UIView!
var TestView:UIView!
override init() {
super.init()
}
init(SourceViews:UIView) {
super.init()
TestView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
TestView.backgroundColor = UIColor.blueColor()
TargetView.addSubview(TestView)
Animator = UIDynamicAnimator(referenceView: TargetView)
let snapBehavior: UISnapBehavior = UISnapBehavior(item: TestView, snapToPoint: TargetView.center)
Animator.addBehavior(snapBehavior)
}
}
"TestView" is added as subview to "Target" but snap behaviour is not working.
I tried the same code in ObjectiveC
#import "DynamicBehaviour.h"
#import <UIKit/UIKit.h>
#interface DynamicBehaviour ()
#property(nonatomic,strong)UISnapBehavior * Snap_behaviour;
#property (nonatomic,strong)UIDynamicAnimator * Animator;
#end
#implementation DynamicBehaviour
-(instancetype)initWithSourceView:(UIView *)TargetView{
if (self = [super init]) {
UIView * TestView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
TestView.backgroundColor = [UIColor blueColor];
[TargetView addSubview:TestView];
self.Animator = [[UIDynamicAnimator alloc]initWithReferenceView:TargetView];
self.Snap_behaviour = [[UISnapBehavior alloc]initWithItem:TestView snapToPoint:TargetView.center];
[self.Animator addBehavior:self.Snap_behaviour];
}
return self;
}
#end
it works fine,the "TestView" snaps to the centre of TargetView.I don't know whats wrong with swift code.
Here's what I've tried:
The dynamic effect is working fine when coded in UIViewController class,problem exist only while subclassing NSObject in swift.
I have tried the other dynamic behaviours such as UIGravityBehavior the same problem exists in swift.
Done the same sample work in ObjectiveC object class its working fine,I've attached the working ObjC code too.
It seems that the problem may exist in init method or variable decleration am not sure about that
However, I don't know how to fix that problem. I've read through many articles on the internet. I've also searched StackOverflow. Please help.

Your code in Swift is not exactly the same as Objective-C code: Objective-C code has strong reference Snap_behaviour, but Swift code has only local readonly variable.
Here is the Swift equivalent:
class DynamicBehaviour: NSObject {
var Animator:UIDynamicAnimator!
var snapBehaviour:UISnapBehavior!
var TargetView:UIView!
var TestView:UIView!
override init() {
super.init()
}
init(SourceViews:UIView) {
super.init()
TestView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
TestView.backgroundColor = UIColor.blueColor()
TargetView.addSubview(TestView)
Animator = UIDynamicAnimator(referenceView: TargetView)
snapBehaviour = UISnapBehavior(item: TestView, snapToPoint: TargetView.center)
Animator.addBehavior(snapBehaviour)
}
}
Usually I use UIDynamicBehaviour as base class for combined behaviors like this
class CombinedBehavior: UIDynamicBehavior {
lazy var collider: UICollisionBehavior = {
let lazilyCreatedCollider = UICollisionBehavior()
lazilyCreatedCollider.translatesReferenceBoundsIntoBoundary = true
lazilyCreatedCollider.collisionMode = UICollisionBehaviorMode.Everything
return lazilyCreatedCollider
}()
lazy var itemBehavior: UIDynamicItemBehavior = {
let lazilyCreatedBehavior = UIDynamicItemBehavior()
lazilyCreatedBehavior.allowsRotation = true
lazilyCreatedBehavior.elasticity = 0
lazilyCreatedBehavior.resistance = 100
lazilyCreatedBehavior.angularResistance = 100
lazilyCreatedBehavior.friction = 100
return lazilyCreatedBehavior
}()
override init() {
super.init()
addChildBehavior(collider)
addChildBehavior(itemBehavior)
}
func addBarrier(path: UIBezierPath, named name: String) {
collider.removeBoundaryWithIdentifier(name)
collider.addBoundaryWithIdentifier(name, forPath: path)
}
func addView(view: UIView) {
dynamicAnimator?.referenceView?.addSubview(view)
collider.addItem(view)
itemBehavior.addItem(view)
}
func removeView(view: UIView) {
collider.removeItem(view)
itemBehavior.removeItem(view)
view.removeFromSuperview()
}
}
Put this code in your controller
lazy var animator: UIDynamicAnimator = {
let lazilyCreatedDynamicAnimator = UIDynamicAnimator(referenceView: self.view) // or any view you needed
// if you need delegate in your controller uncomment this
// lazilyCreatedDynamicAnimator.delegate = self
return lazilyCreatedDynamicAnimator
}()
var combinedBehavior = CombinedBehavior()
override func viewDidLoad() {
// your other code
animator.addBehavior(combinedBehavior)
}

Related

Best way to position UIToolbar programmatically (with or without UIToolbarDelegate)?

I'm implementing in Playgound a segmented control underneath the navigation bar.
This seems to be a classic problem, which has been asked:
UISegmentedControl below UINavigationbar in iOS 7
Add segmented control to navigation bar and keep title with buttons
In the doc of UIBarPositioningDelegate, it says,
The UINavigationBarDelegate, UISearchBarDelegate, and
UIToolbarDelegate protocols extend this protocol to allow for the
positioning of those bars on the screen.
And In the doc of UIBarPosition:
case top
Specifies that the bar is at the top of its containing view.
In the doc of UIToolbar.delegate:
You may not set the delegate when the toolbar is managed by a
navigation controller. The default value is nil.
My current solution is as below (the commented-out code are kept for reference and convenience):
import UIKit
import PlaygroundSupport
class ViewController : UIViewController, UIToolbarDelegate
{
let toolbar : UIToolbar = {
let ret = UIToolbar()
let segmented = UISegmentedControl(items: ["Good", "Bad"])
let barItem = UIBarButtonItem(customView: segmented)
ret.setItems([barItem], animated: false)
return ret
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(toolbar)
// toolbar.delegate = self
}
override func viewDidLayoutSubviews() {
toolbar.frame = CGRect(
x: 0,
y: navigationController?.navigationBar.frame.height ?? 0,
width: navigationController?.navigationBar.frame.width ?? 0,
height: 44
)
}
func position(for bar: UIBarPositioning) -> UIBarPosition {
return .topAttached
}
}
//class Toolbar : UIToolbar {
// override var barPosition: UIBarPosition {
// return .topAttached
// }
//}
let vc = ViewController()
vc.title = "Try"
vc.view.backgroundColor = .red
// Another way to add toolbar...
// let segmented = UISegmentedControl(items: ["Good", "Bad"])
// let barItem = UIBarButtonItem(customView: segmented)
// vc.toolbarItems = [barItem]
// Navigation Controller
let navVC = UINavigationController(navigationBarClass: UINavigationBar.self, toolbarClass: UIToolbar.self)
navVC.pushViewController(vc, animated: true)
navVC.preferredContentSize = CGSize(width: 375, height: 640)
// navVC.isToolbarHidden = false
// Page setup
PlaygroundPage.current.liveView = navVC
PlaygroundPage.current.needsIndefiniteExecution = true
As you can see, this doesn't use a UIToolbarDelegate.
How does a UIToolbarDelegate (providing the position(for:)) come into play in this situation? Since we can always position ourselves (either manually or using Auto Layout), what's the use case of a UIToolbarDelegate?
#Leo Natan's answer in the first question link above mentioned the UIToolbarDelegate, but it seems the toolbar is placed in Interface Builder.
Moreover, if we don't use UIToolbarDelegate here, why don't we just use a plain UIView instead of a UIToolbar?
Try this
UIView *containerVw = [[UIView alloc] initWithFrame:CGRectMake(0, 64, 320, 60)];
containerVw.backgroundColor = UIColorFromRGB(0xffffff);
[self.view addSubview:containerVw];
UIView *bottomView = [[UIView alloc] initWithFrame:CGRectMake(0, 124, 320, 1)];
bottomView.backgroundColor = [UIColor grayColor];
[self.view addSubview:bottomView];
UISegmentedControl *sg = [[UISegmentedControl alloc] initWithItems:#[#"Good", #"Bad"]];
sg.frame = CGRectMake(10, 10, 300, 40);
[view addSubview:sg];
for (UIView *view in self.navigationController.navigationBar.subviews) {
for (UIView *subView in view.subviews) {
[subView isKindOfClass:[UIImageView class]];
subView.hidden = YES;
}
}
By setting the toolbar's delegate and by having the delegate method return .top, you get the normal shadow at the bottom of the toolbar. If you also adjust the toolbars frame one point higher, it will cover the navbar's shadow and the final result will be what appears to be a taller navbar with a segmented control added.
class ViewController : UIViewController, UIToolbarDelegate
{
lazy var toolbar: UIToolbar = {
let ret = UIToolbar()
ret.delegate = self
let segmented = UISegmentedControl(items: ["Good", "Bad"])
let barItem = UIBarButtonItem(customView: segmented)
ret.setItems([barItem], animated: false)
return ret
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(toolbar)
toolbar.delegate = self
}
override func viewDidLayoutSubviews() {
toolbar.frame = CGRect(
x: 0,
y: navigationController?.navigationBar.frame.height - 1 ?? 0,
width: navigationController?.navigationBar.frame.width ?? 0,
height: toolbar.frame.height
)
}
func position(for bar: UIBarPositioning) -> UIBarPosition {
return .top
}
}
How does a UIToolbarDelegate (providing the position(for:)) come into play in this situation? Since we can always position ourselves (either manually or using Auto Layout), what's the use case of a UIToolbarDelegate?
I sincerely do not know how the UIToolbarDelegate comes into play, if you change the UINavigationController.toolbar it will crashes with "You cannot set UIToolbar delegate managed by the UINavigationController manually", moreover the same will happen if you try to change the toolbar's constraint or its translatesAutoresizingMaskIntoConstraints property.
Moreover, if we don't use UIToolbarDelegate here, why don't we just use a plain UIView instead of a UIToolbar?
It seems to be a reasonable question. I guess the answer for this is that you have a UIView subclass which already has the behaviour of UIToolbar, so why would we create another class-like UIToolbar, unless you just want some view below the navigation bar.
There are 2 options that I'm aware of.
1) Related to Move UINavigationController's toolbar to the top to lie underneath navigation bar
The first approach might help when you have to show the toolbar in other ViewControllers that are managed by your NavigationController.
You can subclass UINavigationController and change the Y-axis position of the toolbar when the value is set.
import UIKit
private var context = 0
class NavigationController: UINavigationController {
private var inToolbarFrameChange = false
var observerBag: [NSKeyValueObservation] = []
override func awakeFromNib() {
super.awakeFromNib()
self.inToolbarFrameChange = false
}
override func viewDidLoad() {
super.viewDidLoad()
observerBag.append(
toolbar.observe(\.center, options: .new) { toolbar, _ in
if !self.inToolbarFrameChange {
self.inToolbarFrameChange = true
toolbar.frame = CGRect(
x: 0,
y: self.navigationBar.frame.height + UIApplication.shared.statusBarFrame.height,
width: toolbar.frame.width,
height: toolbar.frame.height
)
self.inToolbarFrameChange = false
}
}
)
}
override func setToolbarHidden(_ hidden: Bool, animated: Bool) {
super.setToolbarHidden(hidden, animated: false)
var rectTB = self.toolbar.frame
rectTB = .zero
}
}
2) You can create your own UIToolbar and add it to view of the UIViewController. Then, you add the constraints to the leading, trailing and the top of the safe area.
import UIKit
final class ViewController: UIViewController {
private let toolbar = UIToolbar()
private let segmentedControl: UISegmentedControl = {
let control = UISegmentedControl(items: ["Op 1", "Op 2"])
control.isEnabled = false
return control
}()
override func loadView() {
super.loadView()
setupToolbar()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationBar.hideBorderLine()
}
private func setupToolbar() {
let barItem = UIBarButtonItem(customView: segmentedControl)
toolbar.setItems([barItem], animated: false)
toolbar.isTranslucent = false
toolbar.isOpaque = false
view.addSubview(toolbar)
toolbar.translatesAutoresizingMaskIntoConstraints = false
toolbar.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
toolbar.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
toolbar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
}
}
private extension UINavigationBar {
func showBorderLine() {
findBorderLine().isHidden = false
}
func hideBorderLine() {
findBorderLine().isHidden = true
}
private func findBorderLine() -> UIImageView! {
return self.subviews
.flatMap { $0.subviews }
.compactMap { $0 as? UIImageView }
.filter { $0.bounds.size.width == self.bounds.size.width }
.filter { $0.bounds.size.height <= 2 }
.first
}
}

Swift addSubview() on views created using init(repeating:count) doesn't work

Here is a ViewController that creates 4 subviews using init(repeating:count).
In viewDidLoad I add them as subviews and set their frames. When I run the application only the last view is added.
class ViewController: UIViewController {
let subviews = [UIView].init(repeating: UIView(), count: 4)
override func viewDidLoad() {
super.viewDidLoad()
for i in 0..<subviews.count {
self.view.addSubview(subviews[i])
self.subviews[i].backgroundColor = UIColor.red
self.subviews[i].frame = CGRect(x: CGFloat(i) * 35, y: 30, width: 30, height: 30)
}
}
}
Here's the same code except instead of using init(repeating:count) I use
a closure. This works fine-- all subviews are added.
class ViewController: UIViewController {
let subviews: [UIView] = {
var subviews = [UIView]()
for i in 0..<4 {
subviews.append(UIView())
}
return subviews
}()
override func viewDidLoad() {
//same as above...
}
}
You’ve put the same instance of UIView in your array four times. Your viewDidLoad just ends up moving that single view around. You need to create four separate instances of UIView.
let subviews = (0 ..< 4).map({ _ in UIView() })

Swift: UITapGestureRecognizer for uiimageview terminate app in custome view

I have some Class extended from base class (MSC_CLItem) like this:
class MSC_CLItem
{
var Type:MSC_CustomListType!
func RenderUI(Point:CGPoint) -> UIView
{
return UIView(frame: CGRect.zero)
}
}
Each extended class must to override RenderUI func to generate itself. All of the extended objects will be added in UIScrollView. Now my problem is:
An uiimageview with TapGesture inside of custom view not detect action. For example my class is:
class MSC_CLItem_Tizer : MSC_CLItem
{
var Title:String!
var Video:MSC_CLItem_TizerVideo!
var Detail:MSC_CLItem_TizerDetail!
private init(title:String!)
{
super.init()
self.Title = title
super.Type = .Tizer
}
override func RenderUI(Point:CGPoint) -> UIView
{
let v = UIImageView()
v.backgroundColor = UIColor.yellowColor()
let screenSize: CGRect = UIScreen.mainScreen().bounds
v.frame.size = CGSize(width: screenSize.width - 10, height: CGFloat(160))
v.frame.origin = Point
let vid = UIImageView()
vid.image = UIImage(named: "default")
vid.backgroundColor = UIColor.grayColor()
vid.contentMode = .ScaleAspectFill
vid.af_setImageWithURL(NSURL(string: "Image HTTP url")!)
vid.frame.size = v.frame.size
vid.frame.origin = CGPoint(x: 0, y: 0)
vid.clipsToBounds = true
vid.userInteractionEnabled = true
v.userInteractionEnabled = true
let tapRecognizer = UITapGestureRecognizer(target: self, action: Selector("imageTapped:"))
vid.addGestureRecognizer(tapRecognizer)
v.addSubview(vid)
}
func imageTapped(gestureRecognizer: UITapGestureRecognizer) {
//Not detected to here
}
}
And when I tap on image the following error occurred:
NSForwarding: warning: object 0x7c8cc830 of class 'MSC_CLItem_Tizer' does not implement methodSignatureForSelector: -- trouble ahead
Unrecognized selector -[MSC_CLItem_Tizer imageTapped:]
I'm very confused of which section of my code is wrong?
Thank you
You must declare the super class as NSObject
class MSC_CLItem : NSObject
{
......
}
let tapRecognizer = UITapGestureRecognizer(target: self, action: Selector("imageTapped:"))
tapGesture.numberOfTouchesRequired = 1;
tapGesture.numberOfTapsRequired = 1;
vid.addGestureRecognizer(tapRecognizer)
v.addSubview(vid)
self.view.addSubview(v)
For more details
Does not implement methodSignatureForSelector: — trouble ahead
Got Unrecognized selector -replacementObjectForKeyedArchiver: crash when implementing NSCoding in Swift [Xcode 6 GM]
I guess here can be a problem. Write this line without ":"
let tapRecognizer = UITapGestureRecognizer(target: self, action: Selector("imageTapped"))

Change existing UIBlurEffect UIBlurEffectStyle programmatically?

I have a Visual Effect View on storyboard connected to my ViewController as an outlet. The effect is burring an ImageView behind it and works great. I'm trying to change the UIBlurEffectStyle from Light to Dark inside a button click IBAction. Any help here would be much appreciated!
#IBOutlet weak var blurView: UIVisualEffectView!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func changeBlurView() {
// This is wrong, but is my best attempt so far.
self.blurView(UIBlurEffectStyle.Dark)
}
While creating my own app I faced to a similar problem. I do not use IB at all, so everything is done programatically. I looked into the UIVisualEffectView.h and it does not provide any effect change on the fly (hopefully this will change in the future).
So here is my solution (I'm using the latest Swift version, so there is an as! operator):
class CustomVisualEffectView : UIVisualEffectView
{
deinit
{
println("UIVisualEffectView will be destroyed.")
}
}
class ViewController: UIViewController
{
var _blurEffect = UIBlurEffect() // global so you can use it for vibrancy effect view as well
var _blurredEffectView = CustomVisualEffectView()
let _someSubView = UIView()
let _someOtherSubView = UIView()
let _effectChanger = UIButton.buttonWithType(.System) as! UIButton
override func viewDidLoad()
{
super.viewDidLoad()
self.view.backgroundColor = UIColor.orangeColor()
/* create a button to change the effect */
_effectChanger.setTitle("Change effect!", forState: UIControlState.Normal)
_effectChanger.frame.size = CGSize(width: 100, height: 20)
_effectChanger.center = self.view.center
_effectChanger.addTarget(self, action: Selector("buttonClicked"), forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(_effectChanger)
/* here is our effect view */
_blurEffect = UIBlurEffect(style: self.randomBlurEfffectStyle())
_blurredEffectView = CustomVisualEffectView(effect: _blurEffect)
self.layoutEffectView()
self.view.addSubview(_blurredEffectView)
/* create two subviews and put them on the effect view */
_someSubView.frame = CGRectMake(10, 10, 10, 10)
_someSubView.backgroundColor = UIColor.redColor()
_blurredEffectView.contentView.addSubview(_someSubView)
_someOtherSubView.frame = CGRectMake(30, 30, 10, 10)
_someOtherSubView.backgroundColor = UIColor.greenColor()
_blurredEffectView.contentView.addSubview(_someOtherSubView)
}
func layoutEffectView()
{
_blurredEffectView.frame.size = CGSize(width: 100, height: 80)
_blurredEffectView.center = CGPointMake(_effectChanger.center.x, _effectChanger.center.y - 50)
}
func buttonClicked()
{
var tempArray = [AnyObject]()
/* get all subviews from the effect view */
for view in _blurredEffectView.contentView.subviews
{
tempArray.append(view)
view.removeFromSuperview()
}
/* modify your effect view */
_blurEffect = UIBlurEffect(style: self.randomBlurEfffectStyle())
/* IMPORTANT: so the old effect view can be destroyed by arc */
_blurredEffectView.removeFromSuperview()
_blurredEffectView = CustomVisualEffectView(effect: _blurEffect)
/* I think this will be really tricky if you will use auto layout */
self.layoutEffectView()
self.view.addSubview(_blurredEffectView)
/* put all subviews back to the effect view*/
for view in tempArray
{
_blurredEffectView.contentView.addSubview(view as! UIView)
}
}
func randomBlurEfffectStyle() -> UIBlurEffectStyle
{
let randomBlurEffectStyle : UIBlurEffectStyle
switch Int(arc4random_uniform(3)) // [0,1,2]
{
case 0:
randomBlurEffectStyle = .ExtraLight
case 1:
randomBlurEffectStyle = .Light
default:
randomBlurEffectStyle = .Dark
}
return randomBlurEffectStyle
}
}

IOS Swift: Using delegate methods in custom class

I am trying to create a bar graph class in swift but having difficulty setting up a delegate....heres what I have so far...
BarChart.swift:
import Foundation
import UIKit
#objc protocol BarChartDelegate {
optional func barChart(colorForBarAtIndex index: Int) -> UIColor
}
class BarChart : BarChartDelegate {
let data : [NSDecimalNumber]
let container = UIView()
var barWidth : CGFloat = 100
var barSpacing : CGFloat = 10
var delegate : BarChartDelegate?
init(data: [NSDecimalNumber], frame: CGRect) {
self.data = data
self.container.frame = frame
drawGraph()
}
func drawGraph() {
var i = 0
for item in self.data {
var bar = UIView()
let xPos = CGFloat(i)*self.barWidth
bar.frame = CGRectMake(xPos, 0, self.barWidth, 100)
if let del = delegate? {
bar.frame
println(del.barChart!(colorForBarAtIndex: i))
bar.backgroundColor = del.barChart!(colorForBarAtIndex: i)
}
else {
println("nope!")
}
self.container.addSubview(bar)
i++
}
}
}
ViewController.swift
class ViewController: UIViewController, BarChartDelegate {
var colors = [
UIColor.greenColor(),
UIColor.blueColor(),
UIColor.redColor()
]
override func viewDidLoad() {
super.viewDidLoad()
var barChart = BarChart(data: [NSDecimalNumber(double: 100.00), NSDecimalNumber(double: 200.00), NSDecimalNumber(double: 300.00)], frame: CGRectMake(0, 0, 400.00, 100.00))
self.view.addSubview(barChart.container)
}
func barChart(colorForBarAtIndex index: Int) -> UIColor { // this is not running?
return self.colors[index]
}
}
the delegate method in my ViewController.swift file is not being run and I just get "nope!" printed to the console 3 times... which is a result of the delegate optional finding nil when unwrapping?
Is there something I am missing here?
Any help would be appreciated!
Thanks,
Dave
First off, it rarely makes sense for a BarChart to also be a BarChartDelegate. You don't delegate things to yourself!
Second, as far as I can tell you're not actually setting the ViewController as the delegate of the BarChart. Simply adopting the BarChartDelegate protocol is not enough to do that; you need to set it explicitly.
So, for example, you might want to do this after you create the BarChart:
var barChart = ...
barChart.delegate = self
Alternatively, if the delegate is vital for your chart, you might want to change the constructor to accept a delegate as an argument.

Resources