How come I can't access the methods of my subview? - ios

In my ViewDidLoad:
let spiralDimension = CGFloat(ScreenWidth! * 0.10)
let spiralName = "spiral.png"
let spiralImage = UIImage(named: spiralName)
spiralView = UIImageView(image: spiralImage!)
spiralView!.frame = CGRect(x: ScreenWidth! / 2 - spiralDimension/2, y: (ScreenHeight!-TabBarHeight!) / 2 - spiralDimension/2 , width: spiralDimension, height: spiralDimension)
spiralView!.tintColor = UIColor.redColor()
self.view.addSubview(spiralView!) //works!!!
Somewhere later...
func fadeBackground(){
UIView.animateWithDuration(self.fadeTime, delay: 0, options: UIViewAnimationOptions.AllowUserInteraction, animations: {
var randomIndex = Int(arc4random_uniform(UInt32(CONSTANTS.MainColorScheme.count)))
var subviews = self.view.subviews
for v in subviews{
if v.isKindOfClass(UIImageView){
println(v) //correctly prints my spiral!
v.tintColor = CONSTANTS.MainColorScheme[randomIndex] //can't do it. XCode won't even auto-complete
}
}
}) { (stuff Bool) -> Void in
}
}
I can't assign tintColor to v, even though it prints my class correctly. Can't build successfully.

subviews returns an array of AnyObject. Thus, you need to cast v in order to set tintColor.
Try:
for v in subviews{
if let v = v as? UIImageView {
println(v)
v.tintColor = CONSTANTS.MainColorScheme[randomIndex]
}
}

You have to typecast it.
if v.isKindOfClass(UIImageView){
let iv = v as! UIImageView
v.tintColor = ...
The problem is that the subviews property is defined in iOS as a variable of type [AnyObject] so Swift doesn't know that your subviews are members of UIView, which has the property tintColor.

Related

Efficient off-screen UIView rendering and mirroring

I have a "off-screen" UIView hierarchy which I want render in different locations of my screen. In addition it should be possible to show only parts of this view hierarchy and should reflect all changes made to this hierarchy.
The difficulties:
The UIView method drawHierarchy(in:afterScreenUpdates:) always calls draw(_ rect:) and is therefore very inefficient for large hierarchies if you want to incorporate all changes to the view hierarchy. You would have to redraw it every screen update or observe all changing properties of all views. Draw view hierarchy documentation
The UIView method snapshotView(afterScreenUpdates:) also does not help much since I have not found a way to get a correct view hierarchy drawing if this hierarchy is "off-screen". Snapshot view documentation
"Off-Screen": The root view of this view hierarchy is not part of the UI of the app. It has no superview.
Below you can see a visual representation of my idea:
Here's how I would go about doing it. First, I would duplicate the view you are trying to duplicate. I wrote a little extension for this:
extension UIView {
func duplicate<T: UIView>() -> T {
return NSKeyedUnarchiver.unarchiveObject(with: NSKeyedArchiver.archivedData(withRootObject: self)) as! T
}
func copyProperties(fromView: UIView, recursive: Bool = true) {
contentMode = fromView.contentMode
tag = fromView.tag
backgroundColor = fromView.backgroundColor
tintColor = fromView.tintColor
layer.cornerRadius = fromView.layer.cornerRadius
layer.maskedCorners = fromView.layer.maskedCorners
layer.borderColor = fromView.layer.borderColor
layer.borderWidth = fromView.layer.borderWidth
layer.shadowOpacity = fromView.layer.shadowOpacity
layer.shadowRadius = fromView.layer.shadowRadius
layer.shadowPath = fromView.layer.shadowPath
layer.shadowColor = fromView.layer.shadowColor
layer.shadowOffset = fromView.layer.shadowOffset
clipsToBounds = fromView.clipsToBounds
layer.masksToBounds = fromView.layer.masksToBounds
mask = fromView.mask
layer.mask = fromView.layer.mask
alpha = fromView.alpha
isHidden = fromView.isHidden
if let gradientLayer = layer as? CAGradientLayer, let fromGradientLayer = fromView.layer as? CAGradientLayer {
gradientLayer.colors = fromGradientLayer.colors
gradientLayer.startPoint = fromGradientLayer.startPoint
gradientLayer.endPoint = fromGradientLayer.endPoint
gradientLayer.locations = fromGradientLayer.locations
gradientLayer.type = fromGradientLayer.type
}
if let imgView = self as? UIImageView, let fromImgView = fromView as? UIImageView {
imgView.tintColor = .clear
imgView.image = fromImgView.image?.withRenderingMode(fromImgView.image?.renderingMode ?? .automatic)
imgView.tintColor = fromImgView.tintColor
}
if let btn = self as? UIButton, let fromBtn = fromView as? UIButton {
btn.setImage(fromBtn.image(for: fromBtn.state), for: fromBtn.state)
}
if let textField = self as? UITextField, let fromTextField = fromView as? UITextField {
if let leftView = fromTextField.leftView {
textField.leftView = leftView.duplicate()
textField.leftView?.copyProperties(fromView: leftView)
}
if let rightView = fromTextField.rightView {
textField.rightView = rightView.duplicate()
textField.rightView?.copyProperties(fromView: rightView)
}
textField.attributedText = fromTextField.attributedText
textField.attributedPlaceholder = fromTextField.attributedPlaceholder
}
if let lbl = self as? UILabel, let fromLbl = fromView as? UILabel {
lbl.attributedText = fromLbl.attributedText
lbl.textAlignment = fromLbl.textAlignment
lbl.font = fromLbl.font
lbl.bounds = fromLbl.bounds
}
if recursive {
for (i, view) in subviews.enumerated() {
if i >= fromView.subviews.count {
break
}
view.copyProperties(fromView: fromView.subviews[i])
}
}
}
}
to use this extension, simply do
let duplicateView = originalView.duplicate()
duplicateView.copyProperties(fromView: originalView)
parentView.addSubview(duplicateView)
Then I would mask the duplicate view to only get the particular section that you want
let mask = UIView(frame: CGRect(x: 0, y: 0, width: yourNewWidth, height: yourNewHeight))
mask.backgroundColor = .black
duplicateView.mask = mask
finally, I would scale it to whatever size you want using CGAffineTransform
duplicateView.transform = CGAffineTransform(scaleX: xScale, y: yScale)
the copyProperties function should work well but you can change it if necessary to copy even more things from one view to another.
Good luck, let me know how it goes :)
I'd duplicate the content I wish to display and crop it as I want.
Let's say I have a ContentViewController which carries the view hierarchy I wish to replicate. I would encapsule all the changes that can be made to the hierarchy inside a ContentViewModel. Something like:
struct ContentViewModel {
let actionTitle: String?
let contentMessage: String?
// ...
}
class ContentViewController: UIViewController {
func display(_ viewModel: ContentViewModel) { /* ... */ }
}
With a ClippingView (or a simple UIScrollView) :
class ClippingView: UIView {
var contentOffset: CGPoint = .zero // a way to specify the part of the view you wish to display
var contentFrame: CGRect = .zero // the actual size of the clipped view
var clippedView: UIView?
override init(frame: CGRect) {
super.init(frame: frame)
clipsToBounds = true
}
override func layoutSubviews() {
super.layoutSubviews()
clippedView?.frame = contentFrame
clippedView?.frame.origin = contentOffset
}
}
And a view controller container, I would crop each instance of my content and update all of them each time something happens :
class ContainerViewController: UIViewController {
let contentViewControllers: [ContentViewController] = // 3 in your case
override func viewDidLoad() {
super.viewDidLoad()
contentViewControllers.forEach { viewController in
addChil(viewController)
let clippingView = ClippingView()
clippingView.clippedView = viewController.view
clippingView.contentOffset = // ...
viewController.didMove(to: self)
}
}
func somethingChange() {
let newViewModel = ContentViewModel(...)
contentViewControllers.forEach { $0.display(newViewModel) }
}
}
Could this scenario work in your case ?

Change UITextView text by using its tag number to identify the element due for change

//UITextView Creation
let textarea = UITextView (frame : CGRect (x: 40, y: 100 ,width:100 , height:100))
textarea.delegate = self
textarea.tag = self.numarr
textarea.backgroundColor = UIColor(red: 0.9686, green: 0.9686, blue: 0.9686, alpha: 1.0)
textarea.layer.cornerRadius = 20.0
textarea.contentInset = UIEdgeInsetsMake(5, 5, 5, 5);
textarea.layer.masksToBounds = true
self.scrollviewxp.addSubview(textarea)
//Later after, the button function
#IBAction func loadbutton(_ sender: Any) {
if let hellatextview = self.scrollviewxp.viewWithTag(index) as? UITextView
{
hellatextview.text = "success"
}
}
The above code is not flagged as an error on Xcode but doesn't change the UITextView (hellatextview) value upon execution. A textView with a tag number (index) exists but isn't being changed.
Any ideas why it isn't working? Ive had the same issue with UIButtons
You only need to make sure that the scrollviewxp is the very
superview for the view trying to get with tag.
Otherwise, there is absolutely 0 reasons to fail.
SWIFT 4 Working solutions:
1.
if let hellaTextView = scrollviewxp.subviews.first(where: { $0.tag == index }) as? UITextView {
hellaTextView.text = "success"
}
or
2.
for subview in scrollviewxp.subviews
{
if let hellatextview.text = subview as? UITextView
{
if hellatextview.tag == index {
hellatextview.text = "success"
}
}
}

Swift 3 - setting a property on an array of views

I was previously able to clean up my code by adding multiple views (UIImageViews, UILabels, and UIButtons) to an array and then iterating through the array to make a property change like this:
var hideViews = [imageView1, imageView2, label1, button1, button2]
for eachView in hideViews {
eachView.isHidden = true
}
which then became in another version of Swift:
var hideViews = [imageView1, imageView2, label1, button1, button2] as [Any]
for eachView in hideViews {
(eachView as AnyObject).isHidden = true
}
I was also able to use this to move several views at once:
for view in viewsToMove {
(view as AnyObject).frame = CGRect(x: view.frame.origin.x - 30, y: view.frame.origin.y, width: view.frame.width, height: view.frame.height)
}
I am now getting the errors:
Cannot assign to immutable expression of type 'Bool!'
Cannot assign to immutable expression of type 'CGRect!'
Does anybody know what I'm missing here, in order to do this in Swift 3?
Thanks!
Given an array of UIView
let hideViews: [UIView] = ...
You can hide each view
hideViews.forEach { $0.isHidden = true }
move each view 30 points to the left
hideViews.forEach { $0.frame.origin.x -= 30 }
or both
hideViews.forEach {
$0.isHidden = true
$0.frame.origin.x -= 30
}
isHidden and frame are properties of UIView class so you should not cast them to AnyObject if you want to update properties that belong to them. Just do:
let views: [UIView] = [imageView1, imageView2, label1, button1, button2]
for view in views {
view.isHidden = true
view.frame = CGRect(x: ..., y: ...)
}
You don't need to force cast to Any or AnyObject.
This work For Swift3:
let v = UIView()
let i = UIImageView()
let l = UILabel()
let b = UIButton()
// Swift auto infers array type, this is equal to write
// let views: [UIView] = [v, i, l, b]
let views = [v, i, l, b]
views.forEach {
$0.isHidden = true
}
//or
for view in views {
view.frame = CGRect.zero
}

Swift 3 - Code Update Problems In a Pod

I updated my project to Swift 3 and I experienced problems in the following pod: https://github.com/Ramotion/expanding-collection and it no longer works :(
The problems are the following, I will explain each problem:
1- "Operator should no longer be declared with body; use a precedence group instead" yellow warning on the following line in the class "Constraints Helper":
infix operator >>>- { associativity left precedence 150}
How to write this in Swift 3?
2-"'>>>-' produces 'NSLayoutConstraint', not the expected contextual result type 'Void' (aka '()')" red error on the following line in the class "Constraints Helper":
func addScaleToFillConstratinsOnView(_ view: UIView) {
[NSLayoutAttribute.left, .right, .top, .bottom].forEach { attribute in
(self, view) >>>- { $0.attribute = attribute }
}
}
How to fix this issue?
3-"Result of operator '>>>-' is unused" yellow warning on the following lines in the AnimatingBarButton class:
fileprivate func configureImageView(_ imageView: UIImageView, imageName: String) {
guard let customView = customView else { return }
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.image = UIImage(named: imageName)
imageView.contentMode = .scaleAspectFit
// imageView.backgroundColor = .greenColor()
// imageView.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
customView.addSubview(imageView)
//Result of operator '>>>-' is unused - HERE 1
[(NSLayoutAttribute.centerX, 12), (.centerY, -1)].forEach { info in
(customView, imageView) >>>- {
$0.attribute = info.0
$0.constant = CGFloat(info.1)
}
}
//Result of operator '>>>-' is unused - HERE 2
[NSLayoutAttribute.height, .width].forEach { attribute in
imageView >>>- {
$0.attribute = attribute
$0.constant = 20
}
}
}
Also I am getting the same error in this piece of code from the BasePageCollectionCell:
fileprivate func createShadowViewOnView(_ view: UIView?) -> UIView? {
guard let view = view else {return nil}
let shadow = Init(UIView(frame: .zero)) {
$0.backgroundColor = .clear
$0.translatesAutoresizingMaskIntoConstraints = false
$0.layer.masksToBounds = false;
$0.layer.shadowColor = UIColor.black.cgColor
$0.layer.shadowRadius = 10
$0.layer.shadowOpacity = 0.3
$0.layer.shadowOffset = CGSize(width: 0, height:0)
}
contentView.insertSubview(shadow, belowSubview: view)
for info: (attribute: NSLayoutAttribute, scale: CGFloat) in [(NSLayoutAttribute.width, 0.8), (NSLayoutAttribute.height, 0.9)] {
if let frontViewConstraint = view.getConstraint(info.attribute) {
//Result of operator '>>>-' is unused
shadow >>>- {
$0.attribute = info.attribute
$0.constant = frontViewConstraint.constant * info.scale
}
}
}
for info: (attribute: NSLayoutAttribute, offset: CGFloat) in [(NSLayoutAttribute.centerX, 0), (NSLayoutAttribute.centerY, 30)] {
//Result of operator '>>>-' is unused
(contentView, shadow, view) >>>- {
$0.attribute = info.attribute
$0.constant = info.offset
}
}
// size shadow
let width = shadow.getConstraint(.width)?.constant
let height = shadow.getConstraint(.height)?.constant
shadow.layer.shadowPath = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: width!, height: height!), cornerRadius: 0).cgPath
return shadow
}
MARK:- PageCollectionView
//Result of operator '>>>-' is unused
collectionView >>>- {
$0.attribute = .height
$0.constant = height
}
How to fix this issue?
I know that this is long, and that the author needs to fix these issues, but the author is not responding or letting us know about an ETA of an update. And I was just about to finish up my app and then updated to Swift 3, I would really appreciate it if you could guide me through this. PS: Many people are facing similar issues in this library, I will share your solutions in the support page of that library.
Best,
Aa

Return Animation Function

I am wanting to create a function so I can easily implement and make modifications to a loading animation that I am using. I am not sure how to return everything I need to properly and get it to display. Here is the code that I used to create it:
let x = (self.view.frame.size.width / 2)
let y = (self.view.frame.size.height / 2)
self.loadingUI = NVActivityIndicatorView(frame: CGRectMake(x, y, 100, 100))
self.loadingUI.center = CGPointMake(view.frame.size.width / 2,
view.frame.size.height / 2)
self.loadingBackground.backgroundColor = UIColor.lightGrayColor()
self.loadingBackground.frame = CGRectMake(0, 0, 150, 150)
self.loadingBackground.center = (self.navigationController?.view.center)!
self.loadingBackground.layer.cornerRadius = 5
self.loadingBackground.layer.opacity = 0.5
self.navigationController?.view.addSubview(loadingBackground)
self.navigationController?.view.addSubview(loadingUI)
self.loadingUI.type = .BallRotateChase
self.loadingUI.color = UIColor.whiteColor()
self.loadingUI.startAnimation()
Is it possible to write a function that would create that so that I can use it multiple time throughout the app? Most everything is in a navigation controller for this custom app.
Here is an easier and simpler version.. I have added everything in viewDidLoad(), but you can make a new seperate function to take care of this.
I have changed the default loader type to ballClipRotateMultiple and default color to blue. Since the color of the load is also white either change bg color or color of loader.
import NVActivityIndicatorView
class ViewController: UIViewController, NVActivityIndicatorViewable {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let x = self.view.center.x
let y = self.view.center.y
let frame = CGRect(x: (x - 50), y: (y - 50), width: 100, height: 100)
let activityIndicatorView = NVActivityIndicatorView(frame: frame)
activityIndicatorView.type = .ballClipRotateMultiple
activityIndicatorView.color = UIColor.blue
self.view.addSubview(activityIndicatorView)
activityIndicatorView.startAnimating()
}
}
Got it,
func loadingAnimation(loadingUI : NVActivityIndicatorView?, loadingBG : UIView, view : UIView, navController : UINavigationController) -> (NVActivityIndicatorView, UIView) {
var loadUI = loadingUI
var loadBG = loadingBG
let x = (view.frame.size.width / 2)
let y = (view.frame.size.height / 2)
loadUI = NVActivityIndicatorView(frame: CGRectMake(x, y, 100, 100))
loadUI!.center = CGPointMake(view.frame.size.width / 2,
view.frame.size.height / 2)
loadBG.backgroundColor = UIColor.lightGrayColor()
loadBG.frame = CGRectMake(0, 0, 150, 150)
loadBG.center = navController.view.center
loadBG.layer.cornerRadius = 5
loadBG.layer.opacity = 0.5
navController.view.addSubview(loadBG)
navController.view.addSubview(loadUI!)
loadUI!.type = .BallRotateChase
loadUI!.color = UIColor.whiteColor()
loadUI!.startAnimation()
return (loadUI!, loadBG)
}
Did the trick!
install pod
pod 'NVActivityIndicatorView'
then
In App Delegate Class
var window: UIWindow?
var objNVLoader = NVLoader()
class func getDelegate() -> AppDelegate
{
return UIApplication.shared.delegate as! AppDelegate
}
then did finish lunch method add
self.window = UIWindow(frame: UIScreen.main.bounds)
then
func showLoader()
{
self.window?.rootViewController!.addChildViewController(objNVLoader)
self.window?.rootViewController!.view!.addSubview(objNVLoader.view!)
objNVLoader.didMove(toParentViewController: self.window?.rootViewController!)
objNVLoader.view.frame=Constants.kScreenBound
self.window?.isUserInteractionEnabled = false
objNVLoader.showLoader()
}
func dismissLoader(){
objNVLoader.removeFromParentViewController()
objNVLoader.didMove(toParentViewController: nil)
objNVLoader.view.removeFromSuperview()
self.window?.isUserInteractionEnabled = true
objNVLoader.dismissLoader()
}
then
AppDelegate.getDelegate().showLoader()
AppDelegate.getDelegate().dismissLoader()

Resources