Scenario: Create and display an overlay (ancillary) UIWindow.
Related Discussion: How do I toggle between UIWindows?
Code:
fileprivate func displayOverLay() {
let myWindow = {() -> UIWindow in
let cWindow = UIWindow(frame: CGRect(x: 10, y: 200, width: 300, height: 300))
cWindow.backgroundColor = UIColor.gray.withAlphaComponent(0.5)
cWindow.tag = 100
return cWindow
}()
myWindow.makeKeyAndVisible()
myWindow.windowLevel = UIWindowLevelAlert
let storyboard = UIStoryboard(name: "Hamburger", bundle: nil)
let vc = storyboard.instantiateInitialViewController()
myWindow.rootViewController = vc
return
}
Here's the shared UIApplication window status:
(lldb) po UIApplication.shared.windows
▿ 2 elements
- 0 : <UIWindow: 0x7fcffc804860; frame = (0 0; 375 667); autoresize = W+H; gestureRecognizers = <NSArray: 0x600000051280>; layer = <UIWindowLayer: 0x60000003e840>>
- 1 : <UIWindow: 0x7fcff9407240; frame = (10 200; 300 300); tag = 100; gestureRecognizers = <NSArray: 0x608000052810>; layer = <UIWindowLayer: 0x608000220420>>
(lldb) po UIApplication.shared.keyWindow!
<UIWindow: 0x7fcff9407240; frame = (10 200; 300 300); tag = 100; gestureRecognizers = <NSArray: 0x608000052810>; layer = <UIWindowLayer: 0x608000220420>>
Here's the result: no gray window sceen
Important Note: If I brake at the end of the displayOverLay() and do a 'po myWindow', and then continue, I do get the display.
So it appears that I'm missing some sort of event to have the UIWindow appear.
What is needed to force the new UIWindow w/contents to actually display?
You need to keep a reference to the created window, otherwise ARC will take care of disposing the window after you return from your function.
You could for instance return the newly created window from your function and store it in a property of the class from which your are calling the function.
Related
I have a simple game in which players get three rounds to achieve the highest score . The gameScene exists inside a SwiftUI View and is created like this:
var gameScene: SKScene {
let scene = NyonindoGameScene(
size: CGSize(
width: UIScreen.main.bounds.width,
height: UIScreen.main.bounds.height
)
)
scene.viewModel = self.viewModel
scene.scaleMode = .aspectFill
return scene
}
It is called from the body of the view (inside a GeometryReader inside a ZStack) using SpriteView(). The code was working great until I tested on a new iPhone 13, which gave me all kinds of quirky and unexpected behaviors. I won't elaborate on them now as I have fixed most, but I am still left with a "phantom" start button. It is designed to display different text depending on the round being played (viz.: "Start," "Try Again," "Last Chance") using a var that is accurately counting rounds. However, I get this at the end of the first round:
When this Frankenstein button gets tapped, the new round begins. HOWEVER, SKPhysicsContactDelegate didBegin(_:) does not get called and collisions are ignored. (In my general bafflement here, I don't know if this is a separate issue or one that will go away when I solve the overlapping button problem.)
In any case, here is the relevant code for the startButton:
func addStartButton(text: String) {
startButton.removeFromParent() // added as one of many failed remedies
let startButtonLabel = SKLabelNode(text: text)
startButtonLabel.fontName = SKFont.bold
startButtonLabel.fontSize = 40.0
startButtonLabel.fontColor = UIColor.white
startButtonLabel.position = CGPoint(x: 0, y: -12)
startButton.position = CGPoint(x:self.frame.midX, y:self.frame.midY)
startButton.zPosition = 3
startButton.addChild(startButtonLabel)
addChild(startButton)
}
The initial start button is called like this in didMove(to view: SKView):
if attempts == 0 {
addStartButton(text: "Start")
}
And the buttons for the second and third round are called inside a gameOver() function like this:
if attempts == 1 {
startButton.removeFromParent() // again, this is overkill as it gets removed before this...
let text: String = "Try Again!"
addStartButton(text: text)
}
if attempts == 2 {
startButton.removeFromParent()
let text: String = "Last Chance!"
addStartButton(text: text)
}
I originally had a switch statement instead of the two if statements, but that generated the same problem. Print statements to the console suggest that only one button is being called for each round, but the results suggest something different.
Any clues? (Apologies if I haven't provided enough code for an assessment.)
why are you removing the button? change it's label:
class TTESTGameScene: SKScene {
var allBoxes: [SKSpriteNode] = []
var startButton: SKShapeNode = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 200, height: 43), cornerRadius: 20)
override func didMove(to view: SKView) {
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
view.allowsTransparency = true
self.backgroundColor = .clear
view.alpha = 1.0
view.isOpaque = true
view.backgroundColor = SKColor.clear.withAlphaComponent(0.0)
let nextButton = SKShapeNode(rect: CGRect(x: 0, y: view.frame.maxY - 40, width: 66, height: 33), cornerRadius: 20)
nextButton.fillColor = .yellow
nextButton.name = "nextButton"
let nextLabel = SKLabelNode(text: "")
nextLabel.fontSize = 40.0
nextLabel.fontColor = UIColor.white
nextLabel.position = CGPoint(x: 0, y: -12)
nextButton.addChild(nextLabel)
addChild(nextButton)
startButton.fillColor = .red
startButton.name = "startButton"
let startButtonLabel = SKLabelNode(text: "000")
startButtonLabel.fontSize = 30.0
startButtonLabel.fontColor = UIColor.white
startButtonLabel.horizontalAlignmentMode = .center
startButtonLabel.position = CGPoint(x: startButton.frame.size.width/2, y: 10)
startButtonLabel.name = "startButtonLabel"
startButton.position = CGPoint(x:self.frame.midX - startButton.frame.size.width/2, y:self.frame.midY)
startButton.zPosition = 3
startButton.addChild(startButtonLabel)
addChild(startButton)
}
var attempts: Int = 0
func nextLevel() {
//startButton.removeFromParent() // added as one of many failed remedies
var text = ""
if attempts == 0 {
text = "Start"
}
else if attempts == 1 {
text = "Try Again!"
}
else if attempts == 2 {
text = "Last Chance!"
}
if let label = startButton.childNode(withName: "//startButtonLabel") as? SKLabelNode {
label.text = text
attempts += 1
attempts = attempts > 2 ? 0:attempts
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let location = touch.location(in: self.view)
let sceneTouchPoint = self.convertPoint(fromView: location)
let touchedNode = self.atPoint(sceneTouchPoint)
print(touchedNode.name)
if touchedNode.name == "nextButton" {
nextLevel()
}
}
}
// A sample SwiftUI creating a GameScene and sizing it
// at 300x400 points
struct TTESTContentView: View {
var scene: SKScene {
let scene = TTESTGameScene()
scene.size = CGSize(width: 300, height: 400)
scene.scaleMode = .aspectFill
return scene
}
var body: some View {
SpriteView(scene: scene)
.frame(width: 300, height: 400)
//.ignoresSafeArea()
}
}
struct ContentViewTest_Previews: PreviewProvider {
static var previews: some View {
TTESTContentView()
}
}
In my application i used AVPlayer and video is playing well for first time, when i go back and again coming to player screen the layer is displaying and video sound also coming but video is not showing. Here is my Code
if (videoPlaying == true) {
videoView.isHidden = false
let targetTime:CMTime = CMTimeMake(0, 1)
videoPlayer.seek(to: targetTime)
videoPlayer.pause()
// audioplayer = nil
playerLayer.removeFromSuperlayer()
playerLayer=AVPlayerLayer(player: videoPlayer)
playerLayer.frame = CGRect(x:0 , y: 75 , width: videoView.frame.size.width , height: UIScreen.main.bounds.height-170)
playerLayer.backgroundColor = UIColor.black.cgColor
playerLayer.videoGravity = AVLayerVideoGravityResize
videoView.layer.addSublayer(playerLayer)
let url = NSURL(string: videoString)
let playerItem1 = AVPlayerItem(url: url! as URL)
videoPlayer.replaceCurrentItem(with: playerItem1)
videoPlayer.play()
AppDelegate.shared().showLoading()
// Setting slider max value
let duration : CMTime = videoPlayer.currentItem!.asset.duration
let seconds : Float64 = CMTimeGetSeconds(duration)
videoSlider.maximumValue = Float(seconds)
videoPlayButton.setImage(UIImage(named: "Pause"), for: .normal)
videoPlaying = true
//Adding Default Periodic Observer On Player
videoPlayer.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, 1), queue: DispatchQueue.main) { (CMTime) -> Void in
if videoPlayer.currentItem?.status == .readyToPlay {
AppDelegate.shared().removeLoading()
self.videoSlider.value = Float(CMTimeGetSeconds(videoPlayer.currentTime()))
let currentTime : Int = Int(CMTimeGetSeconds(videoPlayer.currentTime()))
let minutes = currentTime/60
let seconds = currentTime - minutes * 60
self.durationLabel.text = NSString(format: "%02d:%02d", minutes,seconds) as String
}
}
}
else{
videoView.isHidden = true
let url = NSURL(string: videoString)
let playerItem = AVPlayerItem(url: url! as URL)
videoPlayer=AVPlayer(playerItem: playerItem)
playerLayer=AVPlayerLayer(player: videoPlayer)
playerLayer.frame = CGRect(x:0 , y: 75 , width: videoView.frame.size.width , height: UIScreen.main.bounds.height-170)
playerLayer.backgroundColor = UIColor.black.cgColor
playerLayer.videoGravity = AVLayerVideoGravityResize
videoView.layer.addSublayer(playerLayer)
}
i'm confused that do i have to remove playerlayer every time while coming to player screen? Help me
Thanks in advance
Refrence : https://stackoverflow.com/a/26578895/6619234
how to erase and redraw circle on click evnet?
i tried to call addCircleView method on click event but circle is overlapping every time.
class CircleClosing: UIView {
var circleLayer: CAShapeLayer!
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
// Use UIBezierPath as an easy way to create the CGPath for the layer.
// The path should be the entire circle.
let circlePath : UIBezierPath!
circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 5)/2, startAngle: 0.0, endAngle: CGFloat(M_PI * 2.0), clockwise: true)
// Setup the CAShapeLayer with the path, colors, and line width
circleLayer = CAShapeLayer()
circleLayer.path = circlePath.cgPath
circleLayer.fillColor = UIColor.clear.cgColor
circleLayer.strokeColor = UIColor.blue.cgColor
circleLayer.lineWidth = 20.0;
// Don't draw the circle initially
circleLayer.strokeEnd = 0.0
// Add the circleLayer to the view's layer's sublayers
}
override func layoutSubviews()
{
let frame = self.layer.bounds
circleLayer.frame = frame
layer.addSublayer(circleLayer)
}
required init?(coder aDecoder: NSCoder)
{ super.init(coder: aDecoder) }
func animateCircle(duration: TimeInterval) {
// We want to animate the strokeEnd property of the circleLayer
let animation = CABasicAnimation(keyPath: "strokeEnd")
// Set the animation duration appropriately
animation.duration = duration
// Animate from 0 (no circle) to 1 (full circle)
animation.fromValue = 0
animation.toValue = 1
// Do a linear animation (i.e. the speed of the animation stays the same)
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
// Set the circleLayer's strokeEnd property to 1.0 now so that it's the
// right value when the animation ends.
circleLayer.strokeEnd = 1.0
// Do the actual animation
circleLayer.add(animation, forKey: "animateCircle")
}
}
Add in your subview
func addCircleView() {
var circleView : CircleClosing!
let diceRoll = CGFloat(510) //CGFloat(Int(arc4random_uniform(7))*50)
let diceRolly = CGFloat(70)
let circleWidth = CGFloat(40)
let circleHeight = circleWidth
// Create a new CircleView
circleView = CircleClosing(frame:CGRect(x:diceRoll,y: diceRolly,width: circleWidth,height: circleHeight) )
view.addSubview(circleView)
// Animate the drawing of the circle over the course of 1 second
circleView.animateCircle(duration: 20.0)
}
Thanks in Advance
var circleView : CircleClosing!
func addCircleView() {
let diceRoll = CGFloat(510) //CGFloat(Int(arc4random_uniform(7))*50)
let diceRolly = CGFloat(70)
let circleWidth = CGFloat(40)
let circleHeight = circleWidth
//Add this line here to remove from superview
circleView.removeFromSuperview()
circleView = CircleClosing(frame:CGRect(x:diceRoll,y: diceRolly,width: circleWidth,height: circleHeight) )
view.addSubview(circleView)
// Animate the drawing of the circle over the course of 1 second
circleView.animateCircle(duration: 20.0)
}
How do I make spacing around NSTextAttachments like in the example below?
In the example No spacing is the default behaviour I get when I append a NSTextAttachment to a NSAttributedString.
This worked for me in Swift
public extension NSMutableAttributedString {
func appendSpacing( points : Float ){
// zeroWidthSpace is 200B
let spacing = NSAttributedString(string: "\u{200B}", attributes:[ NSAttributedString.Key.kern: points])
append(spacing)
}
}
The above answers no longer worked for me under iOS 15. So I ended up creating and adding an empty "padding" attachment in between the image attachment and attributed text
let padding = NSTextAttachment()
//Use a height of 0 and width of the padding you want
padding.bounds = CGRect(width: 5, height: 0)
let attachment = NSTextAttachment(image: image)
let attachString = NSAttributedString(attachment: attachment)
//Insert the padding at the front
myAttributedString.insert(NSAttributedString(attachment: padding), at: 0)
//Insert the image before the padding
myAttributedString.insert(attachString, at: 0)
NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] init];
// append NSTextAttachments instance to attributedText...
// then add non-printable string with NSKernAttributeName attributes
unichar c[] = { NSAttachmentCharacter };
NSString *nonprintableString = [NSString stringWithCharacters:c length:1];
NSAttributedString *spacing = [[NSAttributedString alloc] initWithString:nonprintableString attributes:#{
NSKernAttributeName : #(4) // spacing in points
}];
[attributedText appendAttributedString:spacing];
// finally add other text...
Add spacing to image(For iOS 15).
extension UIImage {
func imageWithSpacing(insets: UIEdgeInsets) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(
CGSize(width: self.size.width + insets.left + insets.right,
height: self.size.height + insets.top + insets.bottom), false, self.scale)
UIGraphicsGetCurrentContext()
let origin = CGPoint(x: insets.left, y: insets.top)
self.draw(at: origin)
let imageWithInsets = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return imageWithInsets
}
}
This extension makes it easy to add the space you need. It also works on iOS 16.
extension NSMutableAttributedString {
func appendSpace(_ width: Double) {
let space = NSTextAttachment()
space.bounds = CGRect(x:0, y: 0, width: width, height: 0)
append(NSAttributedString(attachment: space))
}
}
// usages
let example = NSMutableAttributedString()
example.appendSpace(42)
...
Search bar not respond to tap/click?
First create UITableView in UIView
then add search bar in table header
and Cancel button respond, but search label not.
Conf method:
func configureSearchController() {
searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search here..."
searchController.searchBar.delegate = self
searchController.searchBar.sizeToFit()
searchController.searchBar.showsCancelButton = true
tableView.tableHeaderView = searchController.searchBar
}
and call from here:
let dimension = CGRect(x: 0, y: 50, width: Int(dialogContainer.layer.frame.width), height: Int(dialogContainer.layer.frame.height - 100))
self.tableView = UITableView(frame: dimension, style: UITableViewStyle.plain)
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.register(UINib(nibName: "DropdownCell", bundle: nil), forCellReuseIdentifier: "DropdownCell")
self.tableView.backgroundColor = .clear
self.tableView.separatorStyle = UITableViewCellSeparatorStyle.none
configureSearchController() // <-
dialogContainer.addSubview(tableView)
self.tableView.reloadData()