This function gets run when an external screen gets connected to the device (I can print to console when the function runs), but the external screen doesn't display the label and the view isn't white. What am I missing?
func displayConnected(notification: Notification) {
let extScreen = notification.object as! UIScreen
let extWindow = UIWindow(frame: extScreen.bounds)
extWindow.screen = extScreen
let extVC = UIViewController()
extWindow.rootViewController = extVC
let extView = UIView(frame: extWindow.frame)
//customize extView
extView.backgroundColor = UIColor.white
extWindow.addSubview(extView)
extWindow.isHidden = false
let externalLabel = UILabel()
externalLabel.textAlignment = NSTextAlignment.center
externalLabel.font = UIFont(name: "Helvetica", size: 50.0)
externalLabel.frame = extView.bounds
externalLabel.text = "Hello"
externalLabel.textColor = UIColor.red
extView.addSubview(externalLabel)
}
It needs a reference to the window in a local array.
So, add this to your vc class:
var additionalWindows = [UIWindow]()
And this to your function:
self.additionalWindows.append(extWindow)
Related
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 ?
I'm new to iOS development so it may be a simple problem that i can not see, the thing is I have a scrollview inside of a popover and I can't find the way to make it look right.
The problem is highly probably related to the fact that I am trying to use a not full screen popover in iPhone. In this particular case it could be resolved changing that, but I would like to know how to do it, if possible.
Also it's only scrollable sideways and i want it to be only scrollable in the vertical axis. (I didn't look into this yet, so it may be really simple and is not important)
Here is an image of the problem:
Image of how the view doesnt fit in the pop over
There is text to the left of the popover and to the right the image continues
Here is my code
#objc func foo(_ sender: UITapGestureRecognizer) {
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let popupVC = storyboard.instantiateViewController(withIdentifier: "popup")
popupVC.modalTransitionStyle = .crossDissolve
popupVC.modalPresentationStyle = .popover
popupVC.preferredContentSize = CGSize(width: view.bounds.width * 0.75, height: view.bounds.height * 0.75)
let pVC = popupVC.popoverPresentationController
pVC?.permittedArrowDirections = .any
pVC?.delegate = self
pVC?.sourceView = sender.view!
pVC?.sourceRect = sender.view!.bounds
let popView = popupVC.view!
let nosotrosFoto = UIImageView()
nosotrosFoto.image = UIImage(named: "foto.png")
nosotrosFoto.contentMode = UIViewContentMode.scaleAspectFit
nosotrosFoto.translatesAutoresizingMaskIntoConstraints = false
let nosotrosTexto = UILabel()
nosotrosTexto.text = sobreNosotrosString
nosotrosTexto.translatesAutoresizingMaskIntoConstraints = false
nosotrosTexto.numberOfLines = 0
let nosotrosContent = UIView()
nosotrosContent.translatesAutoresizingMaskIntoConstraints = false
nosotrosContent.contentMode = UIViewContentMode.scaleToFill
nosotrosContent.addSubview(nosotrosTexto)
nosotrosContent.addSubview(nosotrosFoto)
nosotrosFoto.topAnchor.constraint(equalTo: nosotrosContent.topAnchor).isActive = true
nosotrosFoto.leftAnchor.constraint(equalTo: nosotrosContent.leftAnchor).isActive = true
nosotrosFoto.rightAnchor.constraint(equalTo: nosotrosContent.rightAnchor).isActive = true
nosotrosTexto.topAnchor.constraint(equalTo: nosotrosFoto.bottomAnchor).isActive = true
nosotrosTexto.leftAnchor.constraint(equalTo: nosotrosFoto.leftAnchor).isActive = true
nosotrosTexto.rightAnchor.constraint(equalTo: nosotrosFoto.rightAnchor).isActive = true
let nosotrosScroll = UIScrollView(frame: popView.bounds)
nosotrosScroll.contentSize = popupVC.preferredContentSize
nosotrosScroll.translatesAutoresizingMaskIntoConstraints = false
nosotrosScroll.contentMode = UIViewContentMode.scaleAspectFit
nosotrosScroll.showsVerticalScrollIndicator = true
nosotrosScroll.backgroundColor = UIColor.blue
nosotrosScroll.addSubview(nosotrosContent)
popView.addSubview(nosotrosScroll)
nosotrosScroll.topAnchor.constraint(equalTo: popView.layoutMarginsGuide.topAnchor).isActive = true
nosotrosScroll.leftAnchor.constraint(equalTo: popView.layoutMarginsGuide.leftAnchor).isActive = true
nosotrosScroll.rightAnchor.constraint(equalTo: popView.layoutMarginsGuide.rightAnchor).isActive = true
nosotrosScroll.bottomAnchor.constraint(equalTo: popView.layoutMarginsGuide.bottomAnchor).isActive = true
self.present(popupVC, animated: true, completion: nil)
}
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return UIModalPresentationStyle.none
}
Actually you miss some constraints for
1- between the nosotrosContent & scrollView
nosotrosContent.topAnchor.constraint(equalTo: nosotrosScroll.layoutMarginsGuide.topAnchor).isActive = true
nosotrosContent.leftAnchor.constraint(equalTo: nosotrosScroll.layoutMarginsGuide.leftAnchor).isActive = true
nosotrosContent.rightAnchor.constraint(equalTo: nosotrosScroll.layoutMarginsGuide.rightAnchor).isActive = true
nosotrosContent.bottomAnchor.constraint(equalTo: nosotrosScroll.layoutMarginsGuide.bottomAnchor).isActive = true
nosotrosContent.widthAnchor.constraint(equalTo: popView.widthAnchor).isActive = true
2- height of the photo , say it 100
nosotrosFoto.heightAnchor.constraint(equalToConstant: 100.0).isActive = true
3- bottom constraint between nosotrosTexto & nosotrosContent
nosotrosTexto.bottomAnchor.constraint(equalTo: nosotrosTexto.bottomAnchor , constant: -20 ).isActive = true
In my presented ViewController i have a button where on the long press I present a ViewController with modalPresentationStyle popover. The thing is I would like to pass the same touch gesture to the popover so that I don't have to make another touch event to make things work. Is it possible in my current situation or do u suggest work around?
interactionViewController?.delegate = self
interactionViewController?.indexpathRow = indexPathRow
interactionViewController?.modalPresentationStyle = .popover
interactionViewController?.popoverPresentationController?.backgroundColor = .clear
interactionViewController?.preferredContentSize = CGSize(width: 320, height: 220)
interactionViewController?.view.layer.cornerRadius = 25
let popoverMenuViewController = interactionViewController?.popoverPresentationController
popoverMenuViewController?.permittedArrowDirections = UIPopoverArrowDirection.init(rawValue: 0)
popoverMenuViewController?.sourceView = sender as? UIView
popoverMenuViewController?.popoverBackgroundViewClass = PopupControllerBackgroundView.self
popoverMenuViewController?.sourceRect = CGRect(x: 0,y:0,width: 1,height: 1)
popoverMenuViewController?.delegate = self
self.present(self.interactionViewController!, animated: false, completion: { [weak self] in
self?.interactionViewController?.view.superview?.layer.cornerRadius = 40
self?.interactionViewController?.view.superview?.backgroundColor = .clear
})
I had created an extension to the UIView and created an ActivityIndicatorView and added it as the subview to UIApplication Window. Now when the device rotates the UIViewController also rotates and not this ActivityIndicatorView.
internal extension UIView{
func showActivityViewWithText(text: String?) -> UIView{
let window = UIApplication.sharedApplication().delegate?.window!!
let baseLineView = window!.viewForBaselineLayout()
let locView = UIView(frame:window!.frame)
locView.backgroundColor = UIColor.clearColor()
locView.center = window!.center
baseLineView.addSubview(locView)
baseLineView.bringSubviewToFront(locView)
let overlay = UIView(frame: locView.frame)
overlay.backgroundColor = UIColor.blackColor()
overlay.alpha = 0.35
locView.addSubview(overlay)
locView.bringSubviewToFront(overlay)
let hud = UIActivityIndicatorView(activityIndicatorStyle: .WhiteLarge)
hud.hidesWhenStopped = true
hud.center = CGPoint(x: locView.frame.size.width/2,
y: locView.frame.size.height/2)
hud.transform = CGAffineTransformMakeScale(1, 1)
hud.color = UIColor.redColor()
hud.startAnimating()
locView.addSubview(hud)
locView.bringSubviewToFront(hud)
}
May be problem is in missed autoresizing mask? Try to add:
hud.autoresizingMask = [ .flexibleTopMargin, .flexibleBottomMargin, .flexibleLeftMargin, .flexibleRightMargin ]
In a reason your hud is subview of a locView autoresizingMask is required for locView too I suppose.
I have a theme class with a few fields.
Background color, font color and element color.
This class also has few static variations of those
class Themes {
static let defaultTheme = Themes(topColor: "5856D6",bottomColor: "C644FC",elementColor: "1F1F21",fontColor: "2B2B2B", name:"one")
static let defaultTheme1 = Themes(topColor: "5AD427",bottomColor: "FFDB4C",elementColor: "1F1F21",fontColor: "2B2B2B", name:"two")
static let defaultTheme2 = Themes(topColor: "FB2B69",bottomColor: "FF5B37",elementColor: "1F1F21",fontColor: "2B2B2B", name:"three")
static let defaultTheme3 = Themes(topColor: "52EDC7",bottomColor: "5AC8FB",elementColor: "1F1F21",fontColor: "2B2B2B", name:"four")
static let defaultTheme4 = Themes(topColor: "5AD427",bottomColor: "FFDB4C",elementColor: "1F1F21",fontColor: "2B2B2B", name:"five")
let topColor:UIColor
let bottomColor:UIColor
let elementColor:UIColor
let fontColor:UIColor
}
My view controller loads the first one by default.
override func viewDidLoad() {
super.viewDidLoad()
self.initView(ThemeManagement.sharedInstance.getTheme())
}
I have a button which changes the theme by going to the next one of the defaults and looping around back to defaultTheme after defaultTheme4
The original run through inside initView is setting the view correctly. But each pass does not update the view. Despite the log messages of the function being printed.
func initView(objectColor : Themes){
println("initing theme "+objectColor.name+"\n"+objectColor.topColor.debugDescription)
let background = CAGradientLayer().gradient(objectColor.topColor, bottomColorCode: objectColor.bottomColor)
background.frame = self.view.bounds
self.view.layer.insertSublayer(background, atIndex: 0)
codes.delegate = self
codes.placeholder = "####"
codes.textAlignment = NSTextAlignment.Center
codes.font = Themes.defaultThemeWithFontSize(.H2)
codes.textColor = objectColor.fontColor
segments.tintColor = objectColor.elementColor
segments.setTitle(String.fontAwesomeIconWithName(.Male), forSegmentAtIndex: 0)
segments.setTitle(String.fontAwesomeIconWithName(.VenusMars), forSegmentAtIndex: 1)
segments.setTitle(String.fontAwesomeIconWithName(.Female), forSegmentAtIndex: 2)
var font = UIFont.fontAwesomeOfSize(fontsize/2);
var attr = NSDictionary(object: font, forKey: NSFontAttributeName)
segments.setTitleTextAttributes(attr as [NSObject : AnyObject], forState:UIControlState.Normal)
acceptButton.titleLabel?.font = UIFont.fontAwesomeOfSize(fontsize)
acceptButton.setTitle(String.fontAwesomeIconWithName(FontAwesome.ArrowCircleORight), forState: .Normal)
acceptButton.tintColor = UIColor.greenColor()
workedButton.setTitle("yes", forState: UIControlState.Normal)
workedButton.tintColor = objectColor.fontColor
workedButton.layer.cornerRadius = 5
workedButton.titleLabel?.font = Themes.defaultThemeWithFontSize(.H2)
workedButton.backgroundColor = UIColor(red:0.333, green:0.937, blue:0.796, alpha: 0.2)
didntWorkButton.setTitle("no", forState: UIControlState.Normal)
didntWorkButton.tintColor = objectColor.fontColor
didntWorkButton.layer.cornerRadius = 5
didntWorkButton.titleLabel?.font = Themes.defaultThemeWithFontSize(.H2)
didntWorkButton.backgroundColor = UIColor(red:0.333, green:0.937, blue:0.796, alpha: 0.2)
locationButton.setTitle("Current Location", forState: UIControlState.Normal)
locationButton.tintColor = objectColor.fontColor
locationButton.layer.cornerRadius = 5
locationButton.titleLabel?.font = Themes.defaultThemeWithFontSize(.H3)
didItWork.text = "Did it work?"
didItWork.font = Themes.defaultThemeWithFontSize(.H2)
didItWork.textColor = objectColor.fontColor
self.view.setNeedsDisplay()
}
Do I have to do something else to get the background layer to update and the font colors to change?
by this
self.view.layer.insertSublayer(background, atIndex: 0)
every time you insert a new background layer behind the old one so you won't see it.
and change the tintColor of a view won't change the view's color immediately so try to change it's textColor direacly
Edit by cripto
The answer above is correct. I just wanted to add my solution to it.
I simply keep a reference to the layer and remove it as long as its not null. This guarantees that I can set it on the first pass and change it every time there after.
if((backgroundLayer) != nil){
backgroundLayer.removeFromSuperlayer()
}
backgroundLayer = CAGradientLayer().gradient(objectColor.topColor, bottomColorCode: objectColor.bottomColor)
backgroundLayer.frame = self.view.bounds
self.view.layer.insertSublayer(backgroundLayer, atIndex: 0)